[amazonechocontrol] Add channels to thermostatController (#13067)

* enhance: add thermostat channels to amazonechocontrol

Signed-off-by: Daniel Campbell <djcampbell79@gmail.com>
This commit is contained in:
Daniel Campbell 2022-08-01 05:48:37 -05:00 committed by GitHub
parent b9782484c6
commit 8d3828a9a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 438 additions and 66 deletions

View File

@ -27,6 +27,7 @@ It provides features to control and view the current state of echo devices:
- change the equalizer settings - change the equalizer settings
- get information about the next alarm, reminder and timer - get information about the next alarm, reminder and timer
- send a message to the echo devices - send a message to the echo devices
- control alexa smart thermostat
It also provides features to control devices connected to your echo: It also provides features to control devices connected to your echo:
@ -58,6 +59,7 @@ Some ideas what you can do in your home by using rules and other openHAB control
- Change the equalizer settings depending on the bluetooth connection - Change the equalizer settings depending on the bluetooth connection
- Turn on a light on your alexa alarm time - Turn on a light on your alexa alarm time
- Activate or deactivate the Alexa Guard with presence detection - Activate or deactivate the Alexa Guard with presence detection
- Adjust thermostat setpoint and mode
With the possibility to control your lights you could do: With the possibility to control your lights you could do:
@ -438,20 +440,25 @@ The only possibility to find out the id is by using the discover function in the
The channels of the smarthome devices will be generated at runtime. Check in the UI thing configurations, which channels are created. The channels of the smarthome devices will be generated at runtime. Check in the UI thing configurations, which channels are created.
| Channel Type ID | Item Type | Access Mode | Thing Type | Description | Channel Type ID | Item Type | Access Mode | Thing Type | Description
|--------------------------|-----------|-------------|-------------------------------|------------------------------------------------------------------------------------------ |--------------------------|----------------------|-------------|-------------------------------|------------------------------------------------------------------------------------------
| powerState | Switch | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the state (ON/OFF) of your device | powerState | Switch | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the state (ON/OFF) of your device
| brightness | Dimmer | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the brightness of your lamp | brightness | Dimmer | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the brightness of your lamp
| color | Color | R | smartHomeDevice, smartHomeDeviceGroup | Shows the color of your light | color | Color | R | smartHomeDevice, smartHomeDeviceGroup | Shows the color of your light
| colorName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the color name of your light (groups are not able to show their color) | colorName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the color name of your light (groups are not able to show their color)
| colorTemperatureName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | White temperatures name of your lights (groups are not able to show their color) | colorTemperatureName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | White temperatures name of your lights (groups are not able to show their color)
| armState | String | R/W | smartHomeDevice, smartHomeDeviceGroup | State of your alarm guard. Options: ARMED_AWAY, ARMED_STAY, ARMED_NIGHT, DISARMED (groups are not able to show their state) | armState | String | R/W | smartHomeDevice, smartHomeDeviceGroup | State of your alarm guard. Options: ARMED_AWAY, ARMED_STAY, ARMED_NIGHT, DISARMED (groups are not able to show their state)
| burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm | burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm
| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm | carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm
| fireAlarm | Contact | R | smartHomeDevice | Fire alarm | fireAlarm | Contact | R | smartHomeDevice | Fire alarm
| waterAlarm | Contact | R | smartHomeDevice | Water alarm | waterAlarm | Contact | R | smartHomeDevice | Water alarm
| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm | glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm
| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm | smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm
| temperature | Number | R | smartHomeDevice | Temperature | temperature | Number:Temperature | R | smartHomeDevice | Temperature
| targetSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat target setpoint
| upperSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat upper setpoint (AUTO)
| lowerSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat lower setpoint (AUTO)
| relativeHumidity | Number:Dimensionless | R | smartHomeDevice | Thermostat humidity
| thermostatMode | String | R/W | smartHomeDevice | Thermostat operation mode
### Example ### Example

View File

@ -145,7 +145,7 @@ public class AccountServlet extends HttpServlet {
} }
Connection connection = this.account.findConnection(); Connection connection = this.account.findConnection();
if (connection != null && uri.equals("/changedomain")) { if (connection != null && "/changedomain".equals(uri)) {
Map<String, String[]> map = req.getParameterMap(); Map<String, String[]> map = req.getParameterMap();
String[] domainArray = map.get("domain"); String[] domainArray = map.get("domain");
if (domainArray == null) { if (domainArray == null) {
@ -199,7 +199,7 @@ public class AccountServlet extends HttpServlet {
postDataBuilder.append(name); postDataBuilder.append(name);
postDataBuilder.append('='); postDataBuilder.append('=');
String value = ""; String value = "";
if (name.equals("failedSignInCount")) { if ("failedSignInCount".equals(name)) {
value = "ape:AA=="; value = "ape:AA==";
} else { } else {
String[] strings = map.get(name); String[] strings = map.get(name);
@ -277,29 +277,29 @@ public class AccountServlet extends HttpServlet {
if (connection != null && connection.verifyLogin()) { if (connection != null && connection.verifyLogin()) {
// handle commands // handle commands
if (baseUrl.equals("/logout") || baseUrl.equals("/logout/")) { if ("/logout".equals(baseUrl) || "/logout/".equals(baseUrl)) {
this.connectionToInitialize = reCreateConnection(); this.connectionToInitialize = reCreateConnection();
this.account.setConnection(null); this.account.setConnection(null);
resp.sendRedirect(this.servletUrl); resp.sendRedirect(this.servletUrl);
return; return;
} }
// handle commands // handle commands
if (baseUrl.equals("/newdevice") || baseUrl.equals("/newdevice/")) { if ("/newdevice".equals(baseUrl) || "/newdevice/".equals(baseUrl)) {
this.connectionToInitialize = new Connection(null, this.gson); this.connectionToInitialize = new Connection(null, this.gson);
this.account.setConnection(null); this.account.setConnection(null);
resp.sendRedirect(this.servletUrl); resp.sendRedirect(this.servletUrl);
return; return;
} }
if (baseUrl.equals("/devices") || baseUrl.equals("/devices/")) { if ("/devices".equals(baseUrl) || "/devices/".equals(baseUrl)) {
handleDevices(resp, connection); handleDevices(resp, connection);
return; return;
} }
if (baseUrl.equals("/changeDomain") || baseUrl.equals("/changeDomain/")) { if ("/changeDomain".equals(baseUrl) || "/changeDomain/".equals(baseUrl)) {
handleChangeDomain(resp, connection); handleChangeDomain(resp, connection);
return; return;
} }
if (baseUrl.equals("/ids") || baseUrl.equals("/ids/")) { if ("/ids".equals(baseUrl) || "/ids/".equals(baseUrl)) {
String serialNumber = getQueryMap(queryString).get("serialNumber"); String serialNumber = getQueryMap(queryString).get("serialNumber");
Device device = account.findDeviceJson(serialNumber); Device device = account.findDeviceJson(serialNumber);
if (device != null) { if (device != null) {
@ -318,7 +318,7 @@ public class AccountServlet extends HttpServlet {
this.connectionToInitialize = connection; this.connectionToInitialize = connection;
} }
if (!uri.equals("/")) { if (!"/".equals(uri)) {
String newUri = req.getServletPath() + "/"; String newUri = req.getServletPath() + "/";
resp.sendRedirect(newUri); resp.sendRedirect(newUri);
return; return;

View File

@ -97,7 +97,7 @@ public class BindingServlet extends HttpServlet {
} }
logger.debug("doGet {}", uri); logger.debug("doGet {}", uri);
if (!uri.equals("/")) { if (!"/".equals(uri)) {
String newUri = req.getServletPath() + "/"; String newUri = req.getServletPath() + "/";
resp.sendRedirect(newUri); resp.sendRedirect(newUri);
return; return;

View File

@ -12,6 +12,8 @@
*/ */
package org.openhab.binding.amazonechocontrol.internal; package org.openhab.binding.amazonechocontrol.internal;
import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
@ -32,10 +34,12 @@ import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.Scanner; import java.util.Scanner;
@ -109,6 +113,7 @@ import org.openhab.binding.amazonechocontrol.internal.jsons.JsonWebSiteCookie;
import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice;
import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.util.HexUtils; import org.openhab.core.util.HexUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -118,6 +123,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
@ -644,7 +650,7 @@ public class Connection {
for (Map.Entry<@Nullable String, List<String>> header : headerFields.entrySet()) { for (Map.Entry<@Nullable String, List<String>> header : headerFields.entrySet()) {
String key = header.getKey(); String key = header.getKey();
if (key != null && !key.isEmpty()) { if (key != null && !key.isEmpty()) {
if (key.equalsIgnoreCase("Set-Cookie")) { if ("Set-Cookie".equalsIgnoreCase(key)) {
// store cookie // store cookie
for (String cookieHeader : header.getValue()) { for (String cookieHeader : header.getValue()) {
if (!cookieHeader.isEmpty()) { if (!cookieHeader.isEmpty()) {
@ -655,7 +661,7 @@ public class Connection {
} }
} }
} }
if (key.equalsIgnoreCase("Location")) { if ("Location".equalsIgnoreCase(key)) {
// get redirect location // get redirect location
location = header.getValue().get(0); location = header.getValue().get(0);
if (!location.isEmpty()) { if (!location.isEmpty()) {
@ -1074,7 +1080,7 @@ public class Connection {
requestObject.add("stateRequests", stateRequests); requestObject.add("stateRequests", stateRequests);
String requestBody = requestObject.toString(); String requestBody = requestObject.toString();
String json = makeRequestAndReturnString("POST", alexaServer + "/api/phoenix/state", requestBody, true, null); String json = makeRequestAndReturnString("POST", alexaServer + "/api/phoenix/state", requestBody, true, null);
logger.trace("Requested {} and received {}", requestBody, json); logger.debug("Requested {} and received {}", requestBody, json);
JsonObject responseObject = Objects.requireNonNull(gson.fromJson(json, JsonObject.class)); JsonObject responseObject = Objects.requireNonNull(gson.fromJson(json, JsonObject.class));
JsonArray deviceStates = (JsonArray) responseObject.get("deviceStates"); JsonArray deviceStates = (JsonArray) responseObject.get("deviceStates");
@ -1164,6 +1170,8 @@ public class Connection {
public void smartHomeCommand(String entityId, String action, @Nullable String property, @Nullable Object value) public void smartHomeCommand(String entityId, String action, @Nullable String property, @Nullable Object value)
throws IOException, InterruptedException { throws IOException, InterruptedException {
String url = alexaServer + "/api/phoenix/state"; String url = alexaServer + "/api/phoenix/state";
Float lowerSetpoint = null;
Float upperSetpoint = null;
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
JsonArray controlRequests = new JsonArray(); JsonArray controlRequests = new JsonArray();
@ -1173,20 +1181,95 @@ public class Connection {
JsonObject parameters = new JsonObject(); JsonObject parameters = new JsonObject();
parameters.addProperty("action", action); parameters.addProperty("action", action);
if (property != null) { if (property != null) {
if (value instanceof QuantityType<?>) { if ("setThermostatMode".equals(action)) {
parameters.addProperty(property + ".value", ((QuantityType<?>) value).floatValue()); if (value instanceof StringType) {
parameters.addProperty(property + ".scale", parameters.addProperty(property + ".value", value.toString());
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius" : "fahrenheit"); }
} else if (value instanceof Boolean) { } else if ("setTargetTemperature".equals(action)) {
parameters.addProperty(property, (boolean) value); if ("targetTemperature".equals(property)) {
} else if (value instanceof String) { if (value instanceof QuantityType<?>) {
parameters.addProperty(property, (String) value); parameters.addProperty(property + ".value", ((QuantityType<?>) value).floatValue());
} else if (value instanceof Number) { parameters.addProperty(property + ".scale",
parameters.addProperty(property, (Number) value); ((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius" : "fahrenheit");
} else if (value instanceof Character) { }
parameters.addProperty(property, (Character) value); } else {
} else if (value instanceof JsonElement) { // Get current upper and lower setpoints to build command syntax
parameters.add(property, (JsonElement) value); Map<String, JsonArray> devices = null;
try {
List<SmartHomeBaseDevice> deviceList = getSmarthomeDeviceList().stream()
.filter(device -> entityId.equals(device.findEntityId())).collect(Collectors.toList());
devices = getSmartHomeDeviceStatesJson(new HashSet<>(deviceList));
} catch (URISyntaxException e) {
logger.debug("{}", e.toString());
}
Entry<String, JsonArray> entry = devices.entrySet().iterator().next();
JsonArray states = entry.getValue();
for (JsonElement stateElement : states) {
JsonObject stateValue = new JsonObject();
String stateJson = stateElement.getAsString();
if (stateJson.startsWith("{") && stateJson.endsWith("}")) {
JsonObject state = Objects.requireNonNull(gson.fromJson(stateJson, JsonObject.class));
String interfaceName = Objects.requireNonNullElse(state.get("namespace"), JsonNull.INSTANCE)
.getAsString();
String name = Objects.requireNonNullElse(state.get("name"), JsonNull.INSTANCE)
.getAsString();
if ("Alexa.ThermostatController".equals(interfaceName)) {
if ("upperSetpoint".equals(name)) {
stateValue = Objects.requireNonNullElse(state.get("value"), JsonNull.INSTANCE)
.getAsJsonObject();
upperSetpoint = Objects
.requireNonNullElse(stateValue.get("value"), JsonNull.INSTANCE)
.getAsFloat();
} else if ("lowerSetpoint".equals(name)) {
stateValue = Objects.requireNonNullElse(state.get("value"), JsonNull.INSTANCE)
.getAsJsonObject();
lowerSetpoint = Objects
.requireNonNullElse(stateValue.get("value"), JsonNull.INSTANCE)
.getAsFloat();
}
}
}
}
if ("lowerSetTemperature".equals(property)) {
if (value instanceof QuantityType<?>) {
parameters.addProperty("upperSetTemperature.value", upperSetpoint);
parameters.addProperty("upperSetTemperature.scale",
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius"
: "fahrenheit");
parameters.addProperty(property + ".value", ((QuantityType<?>) value).floatValue());
parameters.addProperty(property + ".scale",
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius"
: "fahrenheit");
}
} else if ("upperSetTemperature".equals(property)) {
if (value instanceof QuantityType<?>) {
parameters.addProperty(property + ".value", ((QuantityType<?>) value).floatValue());
parameters.addProperty(property + ".scale",
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius"
: "fahrenheit");
parameters.addProperty("lowerSetTemperature.value", lowerSetpoint);
parameters.addProperty("lowerSetTemperature.scale",
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius"
: "fahrenheit");
}
}
}
} else {
if (value instanceof QuantityType<?>) {
parameters.addProperty(property + ".value", ((QuantityType<?>) value).floatValue());
parameters.addProperty(property + ".scale",
((QuantityType<?>) value).getUnit().equals(SIUnits.CELSIUS) ? "celsius" : "fahrenheit");
} else if (value instanceof Boolean) {
parameters.addProperty(property, (boolean) value);
} else if (value instanceof String) {
parameters.addProperty(property, (String) value);
} else if (value instanceof Number) {
parameters.addProperty(property, (Number) value);
} else if (value instanceof Character) {
parameters.addProperty(property, (Character) value);
} else if (value instanceof JsonElement) {
parameters.add(property, (JsonElement) value);
}
} }
} }
controlRequest.add("parameters", parameters); controlRequest.add("parameters", parameters);

View File

@ -30,6 +30,7 @@ import java.util.concurrent.ThreadLocalRandom;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@ -73,10 +74,11 @@ public class WebSocketConnection {
IWebSocketCommandHandler webSocketCommandHandler) throws IOException { IWebSocketCommandHandler webSocketCommandHandler) throws IOException {
this.webSocketCommandHandler = webSocketCommandHandler; this.webSocketCommandHandler = webSocketCommandHandler;
amazonEchoControlWebSocket = new AmazonEchoControlWebSocket(); amazonEchoControlWebSocket = new AmazonEchoControlWebSocket();
webSocketClient = new WebSocketClient(new SslContextFactory.Client()); HttpClient httpClient = new HttpClient(new SslContextFactory.Client());
webSocketClient = new WebSocketClient(httpClient);
try { try {
String host; String host;
if (amazonSite.equalsIgnoreCase("amazon.com")) { if ("amazon.com".equalsIgnoreCase(amazonSite)) {
host = "dp-gw-na-js." + amazonSite; host = "dp-gw-na-js." + amazonSite;
} else { } else {
host = "dp-gw-na." + amazonSite; host = "dp-gw-na." + amazonSite;

View File

@ -139,13 +139,13 @@ public class AmazonEchoDiscovery extends AbstractDiscoveryService {
String deviceFamily = device.deviceFamily; String deviceFamily = device.deviceFamily;
if (deviceFamily != null) { if (deviceFamily != null) {
ThingTypeUID thingTypeId; ThingTypeUID thingTypeId;
if (deviceFamily.equals("ECHO")) { if ("ECHO".equals(deviceFamily)) {
thingTypeId = THING_TYPE_ECHO; thingTypeId = THING_TYPE_ECHO;
} else if (deviceFamily.equals("ROOK")) { } else if ("ROOK".equals(deviceFamily)) {
thingTypeId = THING_TYPE_ECHO_SPOT; thingTypeId = THING_TYPE_ECHO_SPOT;
} else if (deviceFamily.equals("KNIGHT")) { } else if ("KNIGHT".equals(deviceFamily)) {
thingTypeId = THING_TYPE_ECHO_SHOW; thingTypeId = THING_TYPE_ECHO_SHOW;
} else if (deviceFamily.equals("WHA")) { } else if ("WHA".equals(deviceFamily)) {
thingTypeId = THING_TYPE_ECHO_WHA; thingTypeId = THING_TYPE_ECHO_WHA;
} else { } else {
logger.debug("Unknown thing type '{}'", deviceFamily); logger.debug("Unknown thing type '{}'", deviceFamily);

View File

@ -49,7 +49,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService { public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
private AccountHandler accountHandler; private AccountHandler accountHandler;
private final Logger logger = LoggerFactory.getLogger(SmartHomeDevicesDiscovery.class); private Logger logger = LoggerFactory.getLogger(SmartHomeDevicesDiscovery.class);
private @Nullable ScheduledFuture<?> startScanStateJob; private @Nullable ScheduledFuture<?> startScanStateJob;
private @Nullable Long activateTimeStamp; private @Nullable Long activateTimeStamp;
@ -189,6 +189,8 @@ public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
deviceName = "Alexa Color Controller on " + shd.friendlyName; deviceName = "Alexa Color Controller on " + shd.friendlyName;
} else if (interfaces.contains("Alexa.PowerController")) { } else if (interfaces.contains("Alexa.PowerController")) {
deviceName = "Alexa Plug on " + shd.friendlyName; deviceName = "Alexa Plug on " + shd.friendlyName;
} else if (interfaces.contains("Alexa.ThermostatController")) {
deviceName = "Alexa Smart " + shd.friendlyName;
} else { } else {
deviceName = "Unknown Device on " + shd.friendlyName; deviceName = "Unknown Device on " + shd.friendlyName;
} }

View File

@ -783,11 +783,11 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
if (currentNotification != null) { if (currentNotification != null) {
String type = currentNotification.type; String type = currentNotification.type;
if (type != null) { if (type != null) {
if (type.equals("Reminder")) { if ("Reminder".equals(type)) {
updateState(CHANNEL_REMIND, StringType.EMPTY); updateState(CHANNEL_REMIND, StringType.EMPTY);
updateRemind = false; updateRemind = false;
} }
if (type.equals("Alarm")) { if ("Alarm".equals(type)) {
updateState(CHANNEL_PLAY_ALARM_SOUND, StringType.EMPTY); updateState(CHANNEL_PLAY_ALARM_SOUND, StringType.EMPTY);
updateAlarm = false; updateAlarm = false;
} }
@ -864,10 +864,10 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
if (musicProviderId != null) { if (musicProviderId != null) {
musicProviderId = musicProviderId.toUpperCase(); musicProviderId = musicProviderId.toUpperCase();
if (musicProviderId.equals("AMAZON MUSIC")) { if ("AMAZON MUSIC".equals(musicProviderId)) {
musicProviderId = "AMAZON_MUSIC"; musicProviderId = "AMAZON_MUSIC";
} }
if (musicProviderId.equals("CLOUD_PLAYER")) { if ("CLOUD_PLAYER".equals(musicProviderId)) {
musicProviderId = "AMAZON_MUSIC"; musicProviderId = "AMAZON_MUSIC";
} }
if (musicProviderId.startsWith("TUNEIN")) { if (musicProviderId.startsWith("TUNEIN")) {
@ -876,7 +876,7 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler {
if (musicProviderId.startsWith("IHEARTRADIO")) { if (musicProviderId.startsWith("IHEARTRADIO")) {
musicProviderId = "I_HEART_RADIO"; musicProviderId = "I_HEART_RADIO";
} }
if (musicProviderId.equals("APPLE") && musicProviderId.contains("MUSIC")) { if ("APPLE".equals(musicProviderId) && musicProviderId.contains("MUSIC")) {
musicProviderId = "APPLE_MUSIC"; musicProviderId = "APPLE_MUSIC";
} }
} }

View File

@ -347,7 +347,6 @@ public class SmartHomeDeviceHandler extends BaseThingHandler {
if (shd.getCapabilities().stream().map(capability -> capability.interfaceName) if (shd.getCapabilities().stream().map(capability -> capability.interfaceName)
.anyMatch(SUPPORTED_INTERFACES::contains)) { .anyMatch(SUPPORTED_INTERFACES::contains)) {
result.add(shd); result.add(shd);
} }
} else { } else {
SmartHomeGroup shg = (SmartHomeGroup) baseDevice; SmartHomeGroup shg = (SmartHomeGroup) baseDevice;

View File

@ -55,6 +55,11 @@ public class JsonSmartHomeDevices {
return applianceId; return applianceId;
} }
@Override
public @Nullable String findEntityId() {
return entityId;
}
@Override @Override
public boolean isGroup() { public boolean isGroup() {
return false; return false;

View File

@ -39,6 +39,19 @@ public class JsonSmartHomeGroups {
return value; return value;
} }
@Override
public @Nullable String findEntityId() {
SmartHomeGroupIdentifier applianceGroupIdentifier = this.applianceGroupIdentifier;
if (applianceGroupIdentifier == null) {
return null;
}
String value = applianceGroupIdentifier.value;
if (value == null) {
return null;
}
return value;
}
@Override @Override
public boolean isGroup() { public boolean isGroup() {
return true; return true;

View File

@ -25,5 +25,8 @@ public interface SmartHomeBaseDevice {
@Nullable @Nullable
String findId(); String findId();
@Nullable
String findEntityId();
boolean isGroup(); boolean isGroup();
} }

View File

@ -26,23 +26,38 @@ import org.openhab.core.thing.type.ChannelTypeUID;
*/ */
@NonNullByDefault @NonNullByDefault
public class Constants { public class Constants {
public static final Map<String, Function<SmartHomeDeviceHandler, HandlerBase>> HANDLER_FACTORY = Map.of( public static final Map<String, Function<SmartHomeDeviceHandler, HandlerBase>> HANDLER_FACTORY = Map.ofEntries(
HandlerPowerController.INTERFACE, HandlerPowerController::new, HandlerBrightnessController.INTERFACE, Map.entry(HandlerPowerController.INTERFACE, HandlerPowerController::new),
HandlerBrightnessController::new, HandlerColorController.INTERFACE, HandlerColorController::new, Map.entry(HandlerBrightnessController.INTERFACE, HandlerBrightnessController::new),
HandlerColorTemperatureController.INTERFACE, HandlerColorTemperatureController::new, Map.entry(HandlerColorController.INTERFACE, HandlerColorController::new),
HandlerSecurityPanelController.INTERFACE, HandlerSecurityPanelController::new, Map.entry(HandlerColorTemperatureController.INTERFACE, HandlerColorTemperatureController::new),
HandlerAcousticEventSensor.INTERFACE, HandlerAcousticEventSensor::new, HandlerTemperatureSensor.INTERFACE, Map.entry(HandlerSecurityPanelController.INTERFACE, HandlerSecurityPanelController::new),
HandlerTemperatureSensor::new, HandlerThermostatController.INTERFACE, HandlerThermostatController::new, Map.entry(HandlerAcousticEventSensor.INTERFACE, HandlerAcousticEventSensor::new),
HandlerPercentageController.INTERFACE, HandlerPercentageController::new, Map.entry(HandlerTemperatureSensor.INTERFACE, HandlerTemperatureSensor::new),
HandlerPowerLevelController.INTERFACE, HandlerPowerLevelController::new); Map.entry(HandlerThermostatController.INTERFACE, HandlerThermostatController::new),
Map.entry(HandlerPercentageController.INTERFACE, HandlerPercentageController::new),
Map.entry(HandlerPowerLevelController.INTERFACE, HandlerPowerLevelController::new),
Map.entry(HandlerHumiditySensor.INTERFACE, HandlerHumiditySensor::new));
public static final Set<String> SUPPORTED_INTERFACES = HANDLER_FACTORY.keySet(); public static final Set<String> SUPPORTED_INTERFACES = HANDLER_FACTORY.keySet();
// channel types // channel types
public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID( public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "temperature"); AmazonEchoControlBindingConstants.BINDING_ID, "temperature");
public static final ChannelTypeUID CHANNEL_TYPE_HUMIDITY = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "relativeHumidity");
public static final ChannelTypeUID CHANNEL_TYPE_TARGETSETPOINT = new ChannelTypeUID( public static final ChannelTypeUID CHANNEL_TYPE_TARGETSETPOINT = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "targetSetpoint"); AmazonEchoControlBindingConstants.BINDING_ID, "targetSetpoint");
public static final ChannelTypeUID CHANNEL_TYPE_LOWERSETPOINT = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "lowerSetpoint");
public static final ChannelTypeUID CHANNEL_TYPE_UPPERSETPOINT = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "upperSetpoint");
public static final ChannelTypeUID CHANNEL_TYPE_THERMOSTATMODE = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "thermostatMode");
public static final ChannelTypeUID CHANNEL_TYPE_FAN_OPERATION = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "fanOperation");
public static final ChannelTypeUID CHANNEL_TYPE_COOLER_OPERATION = new ChannelTypeUID(
AmazonEchoControlBindingConstants.BINDING_ID, "coolerOperation");
// List of Item types // List of Item types
public static final String ITEM_TYPE_SWITCH = "Switch"; public static final String ITEM_TYPE_SWITCH = "Switch";
@ -50,6 +65,7 @@ public class Constants {
public static final String ITEM_TYPE_STRING = "String"; public static final String ITEM_TYPE_STRING = "String";
public static final String ITEM_TYPE_NUMBER = "Number"; public static final String ITEM_TYPE_NUMBER = "Number";
public static final String ITEM_TYPE_NUMBER_TEMPERATURE = "Number:Temperature"; public static final String ITEM_TYPE_NUMBER_TEMPERATURE = "Number:Temperature";
public static final String ITEM_TYPE_HUMIDITY = "Number:Dimensionless";
public static final String ITEM_TYPE_CONTACT = "Contact"; public static final String ITEM_TYPE_CONTACT = "Contact";
public static final String ITEM_TYPE_COLOR = "Color"; public static final String ITEM_TYPE_COLOR = "Color";
} }

View File

@ -33,6 +33,8 @@ import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.StateDescription; import org.openhab.core.types.StateDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -41,6 +43,9 @@ import com.google.gson.JsonObject;
*/ */
@NonNullByDefault @NonNullByDefault
public abstract class HandlerBase { public abstract class HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerBase.class);
protected SmartHomeDeviceHandler smartHomeDeviceHandler; protected SmartHomeDeviceHandler smartHomeDeviceHandler;
protected Map<String, ChannelInfo> channels = new HashMap<>(); protected Map<String, ChannelInfo> channels = new HashMap<>();
@ -76,6 +81,7 @@ public abstract class HandlerBase {
if (properties != null) { if (properties != null) {
List<JsonSmartHomeCapabilities.Property> supported = Objects.requireNonNullElse(properties.supported, List<JsonSmartHomeCapabilities.Property> supported = Objects.requireNonNullElse(properties.supported,
List.of()); List.of());
logger.trace("{} | {}", capability.toString(), supported.toString());
for (Property property : supported) { for (Property property : supported) {
String name = property.name; String name = property.name;
if (name != null) { if (name != null) {

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.amazonechocontrol.internal.smarthome;
import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.*;
import static org.openhab.core.library.unit.Units.*;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.amazonechocontrol.internal.Connection;
import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler;
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability;
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
/**
* The {@link HandlerHumiditySensor} is responsible for the Alexa.HumiditySensorInterface
*
* @author Daniel Campbell - Initial contribution
*/
@NonNullByDefault
public class HandlerHumiditySensor extends HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerHumiditySensor.class);
// Interface
public static final String INTERFACE = "Alexa.HumiditySensor";
// Channel definitions
private static final ChannelInfo HUMIDITY = new ChannelInfo("relativeHumidity" /* propertyName */ ,
"relativeHumidity" /* ChannelId */, CHANNEL_TYPE_HUMIDITY /* Channel Type */ ,
ITEM_TYPE_HUMIDITY /* Item Type */);
public HandlerHumiditySensor(SmartHomeDeviceHandler smartHomeDeviceHandler) {
super(smartHomeDeviceHandler);
}
@Override
public String[] getSupportedInterface() {
return new String[] { INTERFACE };
}
@Override
protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) {
if (HUMIDITY.propertyName.equals(property)) {
return new ChannelInfo[] { HUMIDITY };
}
return null;
}
@Override
public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) {
for (JsonObject state : stateList) {
State humidityValue = null;
logger.debug("Updating {} with state: {}", interfaceName, state.toString());
if (HUMIDITY.propertyName.equals(state.get("name").getAsString())) {
// For groups take the first
humidityValue = getQuantityTypeState(state.get("value").getAsInt(), PERCENT);
updateState(HUMIDITY.channelId, humidityValue == null ? UnDefType.UNDEF : humidityValue);
}
}
}
protected State getQuantityTypeState(@Nullable Number value, Unit<?> unit) {
return (value == null) ? UnDefType.UNDEF : new QuantityType<>(value, unit);
}
@Override
public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId,
List<SmartHomeCapability> capabilities, String channelId, Command command) throws IOException {
return false;
}
@Override
public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription,
@Nullable Locale locale) {
return null;
}
}

View File

@ -32,17 +32,21 @@ import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateDescription; import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
/** /**
* The {@link HandlerTemperatureSensor} is responsible for the Alexa.PowerControllerInterface * The {@link HandlerTemperatureSensor} is responsible for the Alexa.TemperatureSensorInterface
* *
* @author Lukas Knoeller - Initial contribution * @author Lukas Knoeller - Initial contribution
* @author Michael Geramb - Initial contribution * @author Michael Geramb - Initial contribution
*/ */
@NonNullByDefault @NonNullByDefault
public class HandlerTemperatureSensor extends HandlerBase { public class HandlerTemperatureSensor extends HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerTemperatureSensor.class);
// Interface // Interface
public static final String INTERFACE = "Alexa.TemperatureSensor"; public static final String INTERFACE = "Alexa.TemperatureSensor";
// Channel definitions // Channel definitions
@ -71,6 +75,7 @@ public class HandlerTemperatureSensor extends HandlerBase {
public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) { public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) {
QuantityType<Temperature> temperatureValue = null; QuantityType<Temperature> temperatureValue = null;
for (JsonObject state : stateList) { for (JsonObject state : stateList) {
logger.debug("Updating {} with state: {}", interfaceName, state.toString());
if (TEMPERATURE.propertyName.equals(state.get("name").getAsString())) { if (TEMPERATURE.propertyName.equals(state.get("name").getAsString())) {
JsonObject value = state.get("value").getAsJsonObject(); JsonObject value = state.get("value").getAsJsonObject();
// For groups take the first // For groups take the first

View File

@ -27,11 +27,14 @@ import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHan
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability;
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.StateDescription; import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -42,12 +45,23 @@ import com.google.gson.JsonObject;
*/ */
@NonNullByDefault @NonNullByDefault
public class HandlerThermostatController extends HandlerBase { public class HandlerThermostatController extends HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerThermostatController.class);
// Interface // Interface
public static final String INTERFACE = "Alexa.ThermostatController"; public static final String INTERFACE = "Alexa.ThermostatController";
// Channel definitions // Channel definitions
private static final ChannelInfo TARGET_SETPOINT = new ChannelInfo("targetSetpoint" /* propertyName */ , private static final ChannelInfo TARGET_SETPOINT = new ChannelInfo("targetSetpoint" /* propertyName */ ,
"targetSetpoint" /* ChannelId */, CHANNEL_TYPE_TARGETSETPOINT /* Channel Type */ , "targetSetpoint" /* ChannelId */, CHANNEL_TYPE_TARGETSETPOINT /* Channel Type */ ,
ITEM_TYPE_NUMBER_TEMPERATURE /* Item Type */); ITEM_TYPE_NUMBER_TEMPERATURE /* Item Type */);
private static final ChannelInfo LOWER_SETPOINT = new ChannelInfo("lowerSetpoint" /* propertyName */ ,
"lowerSetpoint" /* ChannelId */, CHANNEL_TYPE_LOWERSETPOINT /* Channel Type */ ,
ITEM_TYPE_NUMBER_TEMPERATURE /* Item Type */);
private static final ChannelInfo UPPER_SETPOINT = new ChannelInfo("upperSetpoint" /* propertyName */ ,
"upperSetpoint" /* ChannelId */, CHANNEL_TYPE_UPPERSETPOINT /* Channel Type */ ,
ITEM_TYPE_NUMBER_TEMPERATURE /* Item Type */);
private static final ChannelInfo THERMOSTAT_MODE = new ChannelInfo("thermostatMode" /* propertyName */ ,
"thermostatMode" /* ChannelId */, CHANNEL_TYPE_THERMOSTATMODE /* Channel Type */ ,
ITEM_TYPE_STRING /* Item Type */);
public HandlerThermostatController(SmartHomeDeviceHandler smartHomeDeviceHandler) { public HandlerThermostatController(SmartHomeDeviceHandler smartHomeDeviceHandler) {
super(smartHomeDeviceHandler); super(smartHomeDeviceHandler);
@ -63,17 +77,27 @@ public class HandlerThermostatController extends HandlerBase {
if (TARGET_SETPOINT.propertyName.equals(property)) { if (TARGET_SETPOINT.propertyName.equals(property)) {
return new ChannelInfo[] { TARGET_SETPOINT }; return new ChannelInfo[] { TARGET_SETPOINT };
} }
if (LOWER_SETPOINT.propertyName.equals(property)) {
return new ChannelInfo[] { LOWER_SETPOINT };
}
if (UPPER_SETPOINT.propertyName.equals(property)) {
return new ChannelInfo[] { UPPER_SETPOINT };
}
if (THERMOSTAT_MODE.propertyName.equals(property)) {
return new ChannelInfo[] { THERMOSTAT_MODE };
}
return null; return null;
} }
@Override @Override
public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) { public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) {
QuantityType<Temperature> temperatureValue = null;
for (JsonObject state : stateList) { for (JsonObject state : stateList) {
QuantityType<Temperature> temperatureValue = null;
logger.debug("Updating {} with state: {}", interfaceName, state.toString());
if (TARGET_SETPOINT.propertyName.equals(state.get("name").getAsString())) { if (TARGET_SETPOINT.propertyName.equals(state.get("name").getAsString())) {
JsonObject value = state.get("value").getAsJsonObject();
// For groups take the first // For groups take the first
if (temperatureValue == null) { if (temperatureValue == null) {
JsonObject value = state.get("value").getAsJsonObject();
float temperature = value.get("value").getAsFloat(); float temperature = value.get("value").getAsFloat();
String scale = value.get("scale").getAsString().toUpperCase(); String scale = value.get("scale").getAsString().toUpperCase();
if ("CELSIUS".equals(scale)) { if ("CELSIUS".equals(scale)) {
@ -82,9 +106,43 @@ public class HandlerThermostatController extends HandlerBase {
temperatureValue = new QuantityType<Temperature>(temperature, ImperialUnits.FAHRENHEIT); temperatureValue = new QuantityType<Temperature>(temperature, ImperialUnits.FAHRENHEIT);
} }
} }
updateState(TARGET_SETPOINT.channelId, temperatureValue == null ? UnDefType.UNDEF : temperatureValue);
}
if (THERMOSTAT_MODE.propertyName.equals(state.get("name").getAsString())) {
// For groups take the first
String operation = state.get("value").getAsString().toUpperCase();
StringType operationValue = new StringType(operation);
updateState(THERMOSTAT_MODE.channelId, operationValue);
}
if (UPPER_SETPOINT.propertyName.equals(state.get("name").getAsString())) {
// For groups take the first
if (temperatureValue == null) {
JsonObject value = state.get("value").getAsJsonObject();
float temperature = value.get("value").getAsFloat();
String scale = value.get("scale").getAsString().toUpperCase();
if ("CELSIUS".equals(scale)) {
temperatureValue = new QuantityType<Temperature>(temperature, SIUnits.CELSIUS);
} else {
temperatureValue = new QuantityType<Temperature>(temperature, ImperialUnits.FAHRENHEIT);
}
}
updateState(UPPER_SETPOINT.channelId, temperatureValue == null ? UnDefType.UNDEF : temperatureValue);
}
if (LOWER_SETPOINT.propertyName.equals(state.get("name").getAsString())) {
// For groups take the first
if (temperatureValue == null) {
JsonObject value = state.get("value").getAsJsonObject();
float temperature = value.get("value").getAsFloat();
String scale = value.get("scale").getAsString().toUpperCase();
if ("CELSIUS".equals(scale)) {
temperatureValue = new QuantityType<Temperature>(temperature, SIUnits.CELSIUS);
} else {
temperatureValue = new QuantityType<Temperature>(temperature, ImperialUnits.FAHRENHEIT);
}
}
updateState(LOWER_SETPOINT.channelId, temperatureValue == null ? UnDefType.UNDEF : temperatureValue);
} }
} }
updateState(TARGET_SETPOINT.channelId, temperatureValue == null ? UnDefType.UNDEF : temperatureValue);
} }
@Override @Override
@ -99,6 +157,30 @@ public class HandlerThermostatController extends HandlerBase {
} }
} }
} }
if (channelId.equals(LOWER_SETPOINT.channelId)) {
if (containsCapabilityProperty(capabilities, LOWER_SETPOINT.propertyName)) {
if (command instanceof QuantityType) {
connection.smartHomeCommand(entityId, "setTargetTemperature", "lowerSetTemperature", command);
return true;
}
}
}
if (channelId.equals(UPPER_SETPOINT.channelId)) {
if (containsCapabilityProperty(capabilities, UPPER_SETPOINT.propertyName)) {
if (command instanceof QuantityType) {
connection.smartHomeCommand(entityId, "setTargetTemperature", "upperSetTemperature", command);
return true;
}
}
}
if (channelId.equals(THERMOSTAT_MODE.channelId)) {
if (containsCapabilityProperty(capabilities, THERMOSTAT_MODE.propertyName)) {
if (command instanceof StringType) {
connection.smartHomeCommand(entityId, "setThermostatMode", "thermostatMode", command);
return true;
}
}
}
return false; return false;
} }

View File

@ -101,6 +101,8 @@ channel-type.amazonechocontrol.lastVoiceCommand.label = Last Voice Command
channel-type.amazonechocontrol.lastVoiceCommand.description = Last voice command spoken to the device. Writing to the channel starts voice output. channel-type.amazonechocontrol.lastVoiceCommand.description = Last voice command spoken to the device. Writing to the channel starts voice output.
channel-type.amazonechocontrol.loop.label = Loop channel-type.amazonechocontrol.loop.label = Loop
channel-type.amazonechocontrol.loop.description = Loop channel-type.amazonechocontrol.loop.description = Loop
channel-type.amazonechocontrol.lowerSetpoint.label = Lower Setpoint
channel-type.amazonechocontrol.lowerSetpoint.description = Lower Setpoint
channel-type.amazonechocontrol.mediaLength.label = Media Length channel-type.amazonechocontrol.mediaLength.label = Media Length
channel-type.amazonechocontrol.mediaLength.description = Media length channel-type.amazonechocontrol.mediaLength.description = Media length
channel-type.amazonechocontrol.mediaProgress.label = Media Progress channel-type.amazonechocontrol.mediaProgress.label = Media Progress
@ -139,6 +141,8 @@ channel-type.amazonechocontrol.radio.label = TuneIn Radio
channel-type.amazonechocontrol.radio.description = Radio turned on channel-type.amazonechocontrol.radio.description = Radio turned on
channel-type.amazonechocontrol.radioStationId.label = TuneIn Radio Station Id channel-type.amazonechocontrol.radioStationId.label = TuneIn Radio Station Id
channel-type.amazonechocontrol.radioStationId.description = Id of the radio station channel-type.amazonechocontrol.radioStationId.description = Id of the radio station
channel-type.amazonechocontrol.relativeHumidity.label = Humidity
channel-type.amazonechocontrol.relativeHumidity.description = Relative humidity measured by the thermostat.
channel-type.amazonechocontrol.remind.label = Remind channel-type.amazonechocontrol.remind.label = Remind
channel-type.amazonechocontrol.remind.description = Speak the reminder and send a notification to the Alexa app channel-type.amazonechocontrol.remind.description = Speak the reminder and send a notification to the Alexa app
channel-type.amazonechocontrol.save.label = Save channel-type.amazonechocontrol.save.label = Save
@ -173,8 +177,12 @@ channel-type.amazonechocontrol.textToSpeech.label = Speak
channel-type.amazonechocontrol.textToSpeech.description = Speak the text (Write only). It is possible to use plain text or SSML: <speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect>.Can you believe it?</speak> channel-type.amazonechocontrol.textToSpeech.description = Speak the text (Write only). It is possible to use plain text or SSML: <speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect>.Can you believe it?</speak>
channel-type.amazonechocontrol.textToSpeechVolume.label = Speak Volume channel-type.amazonechocontrol.textToSpeechVolume.label = Speak Volume
channel-type.amazonechocontrol.textToSpeechVolume.description = Volume of the Speak channel. If 0, the current volume will be used. channel-type.amazonechocontrol.textToSpeechVolume.description = Volume of the Speak channel. If 0, the current volume will be used.
channel-type.amazonechocontrol.thermostatMode.label = Thermostat Mode
channel-type.amazonechocontrol.thermostatMode.description = Thermostat Mode
channel-type.amazonechocontrol.title.label = Title channel-type.amazonechocontrol.title.label = Title
channel-type.amazonechocontrol.title.description = Title channel-type.amazonechocontrol.title.description = Title
channel-type.amazonechocontrol.upperSetpoint.label = Upper Setpoint
channel-type.amazonechocontrol.upperSetpoint.description = Upper Setpoint
channel-type.amazonechocontrol.volume.label = Volume channel-type.amazonechocontrol.volume.label = Volume
channel-type.amazonechocontrol.volume.description = Volume of the sound channel-type.amazonechocontrol.volume.description = Volume of the sound
channel-type.amazonechocontrol.waterAlarm.label = Water Alarm channel-type.amazonechocontrol.waterAlarm.label = Water Alarm

View File

@ -634,6 +634,18 @@
<description>Temperature</description> <description>Temperature</description>
<state readOnly="true" pattern="%.1f %unit%"/> <state readOnly="true" pattern="%.1f %unit%"/>
</channel-type> </channel-type>
<!-- Alexa.HumiditySensor -->
<channel-type id="relativeHumidity">
<item-type>Number:Dimensionless</item-type>
<label>Humidity</label>
<description>Relative humidity measured by the thermostat.</description>
<category>Humidity</category>
<tags>
<tag>Measurement</tag>
<tag>Temperature</tag>
</tags>
<state readOnly="true" pattern=":.1%"/>
</channel-type>
<!-- Alexa.ThermostatController --> <!-- Alexa.ThermostatController -->
<channel-type id="targetSetpoint"> <channel-type id="targetSetpoint">
<item-type>Number:Temperature</item-type> <item-type>Number:Temperature</item-type>
@ -641,4 +653,32 @@
<description>Target Setpoint</description> <description>Target Setpoint</description>
<state pattern="%.1f %unit%"/> <state pattern="%.1f %unit%"/>
</channel-type> </channel-type>
<channel-type id="upperSetpoint">
<item-type>Number:Temperature</item-type>
<label>Upper Setpoint</label>
<description>Upper Setpoint</description>
<category>Temperature</category>
<tags>
<tag>Setpoint</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="lowerSetpoint">
<item-type>Number:Temperature</item-type>
<label>Lower Setpoint</label>
<description>Lower Setpoint</description>
<category>Temperature</category>
<tags>
<tag>Setpoint</tag>
<tag>Temperature</tag>
</tags>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="thermostatMode">
<item-type>String</item-type>
<label>Thermostat Mode</label>
<description>Thermostat Mode</description>
<state pattern="%s"/>
</channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>