[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
- get information about the next alarm, reminder and timer
- send a message to the echo devices
- control alexa smart thermostat
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
- Turn on a light on your alexa alarm time
- Activate or deactivate the Alexa Guard with presence detection
- Adjust thermostat setpoint and mode
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.
| 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
| 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
| 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)
| 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
| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm
| fireAlarm | Contact | R | smartHomeDevice | Fire alarm
| waterAlarm | Contact | R | smartHomeDevice | Water alarm
| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm
| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm
| temperature | Number | R | smartHomeDevice | Temperature
|--------------------------|----------------------|-------------|-------------------------------|------------------------------------------------------------------------------------------
| 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
| 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)
| 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)
| burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm
| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm
| fireAlarm | Contact | R | smartHomeDevice | Fire alarm
| waterAlarm | Contact | R | smartHomeDevice | Water alarm
| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm
| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm
| 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

View File

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

View File

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

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.amazonechocontrol.internal;
import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
@ -32,10 +34,12 @@ import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Random;
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.core.common.ThreadPoolManager;
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.util.HexUtils;
import org.slf4j.Logger;
@ -118,6 +123,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
@ -644,7 +650,7 @@ public class Connection {
for (Map.Entry<@Nullable String, List<String>> header : headerFields.entrySet()) {
String key = header.getKey();
if (key != null && !key.isEmpty()) {
if (key.equalsIgnoreCase("Set-Cookie")) {
if ("Set-Cookie".equalsIgnoreCase(key)) {
// store cookie
for (String cookieHeader : header.getValue()) {
if (!cookieHeader.isEmpty()) {
@ -655,7 +661,7 @@ public class Connection {
}
}
}
if (key.equalsIgnoreCase("Location")) {
if ("Location".equalsIgnoreCase(key)) {
// get redirect location
location = header.getValue().get(0);
if (!location.isEmpty()) {
@ -1074,7 +1080,7 @@ public class Connection {
requestObject.add("stateRequests", stateRequests);
String requestBody = requestObject.toString();
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));
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)
throws IOException, InterruptedException {
String url = alexaServer + "/api/phoenix/state";
Float lowerSetpoint = null;
Float upperSetpoint = null;
JsonObject json = new JsonObject();
JsonArray controlRequests = new JsonArray();
@ -1173,20 +1181,95 @@ public class Connection {
JsonObject parameters = new JsonObject();
parameters.addProperty("action", action);
if (property != null) {
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);
if ("setThermostatMode".equals(action)) {
if (value instanceof StringType) {
parameters.addProperty(property + ".value", value.toString());
}
} else if ("setTargetTemperature".equals(action)) {
if ("targetTemperature".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");
}
} else {
// Get current upper and lower setpoints to build command syntax
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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,6 +39,19 @@ public class JsonSmartHomeGroups {
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
public boolean isGroup() {
return true;

View File

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

View File

@ -26,23 +26,38 @@ import org.openhab.core.thing.type.ChannelTypeUID;
*/
@NonNullByDefault
public class Constants {
public static final Map<String, Function<SmartHomeDeviceHandler, HandlerBase>> HANDLER_FACTORY = Map.of(
HandlerPowerController.INTERFACE, HandlerPowerController::new, HandlerBrightnessController.INTERFACE,
HandlerBrightnessController::new, HandlerColorController.INTERFACE, HandlerColorController::new,
HandlerColorTemperatureController.INTERFACE, HandlerColorTemperatureController::new,
HandlerSecurityPanelController.INTERFACE, HandlerSecurityPanelController::new,
HandlerAcousticEventSensor.INTERFACE, HandlerAcousticEventSensor::new, HandlerTemperatureSensor.INTERFACE,
HandlerTemperatureSensor::new, HandlerThermostatController.INTERFACE, HandlerThermostatController::new,
HandlerPercentageController.INTERFACE, HandlerPercentageController::new,
HandlerPowerLevelController.INTERFACE, HandlerPowerLevelController::new);
public static final Map<String, Function<SmartHomeDeviceHandler, HandlerBase>> HANDLER_FACTORY = Map.ofEntries(
Map.entry(HandlerPowerController.INTERFACE, HandlerPowerController::new),
Map.entry(HandlerBrightnessController.INTERFACE, HandlerBrightnessController::new),
Map.entry(HandlerColorController.INTERFACE, HandlerColorController::new),
Map.entry(HandlerColorTemperatureController.INTERFACE, HandlerColorTemperatureController::new),
Map.entry(HandlerSecurityPanelController.INTERFACE, HandlerSecurityPanelController::new),
Map.entry(HandlerAcousticEventSensor.INTERFACE, HandlerAcousticEventSensor::new),
Map.entry(HandlerTemperatureSensor.INTERFACE, HandlerTemperatureSensor::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();
// channel types
public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID(
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(
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
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_NUMBER = "Number";
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_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.State;
import org.openhab.core.types.StateDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
@ -41,6 +43,9 @@ import com.google.gson.JsonObject;
*/
@NonNullByDefault
public abstract class HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerBase.class);
protected SmartHomeDeviceHandler smartHomeDeviceHandler;
protected Map<String, ChannelInfo> channels = new HashMap<>();
@ -76,6 +81,7 @@ public abstract class HandlerBase {
if (properties != null) {
List<JsonSmartHomeCapabilities.Property> supported = Objects.requireNonNullElse(properties.supported,
List.of());
logger.trace("{} | {}", capability.toString(), supported.toString());
for (Property property : supported) {
String name = property.name;
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.StateDescription;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 Michael Geramb - Initial contribution
*/
@NonNullByDefault
public class HandlerTemperatureSensor extends HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerTemperatureSensor.class);
// Interface
public static final String INTERFACE = "Alexa.TemperatureSensor";
// Channel definitions
@ -71,6 +75,7 @@ public class HandlerTemperatureSensor extends HandlerBase {
public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) {
QuantityType<Temperature> temperatureValue = null;
for (JsonObject state : stateList) {
logger.debug("Updating {} with state: {}", interfaceName, state.toString());
if (TEMPERATURE.propertyName.equals(state.get("name").getAsString())) {
JsonObject value = state.get("value").getAsJsonObject();
// 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.JsonSmartHomeDevices.SmartHomeDevice;
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.SIUnits;
import org.openhab.core.types.Command;
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;
@ -42,12 +45,23 @@ import com.google.gson.JsonObject;
*/
@NonNullByDefault
public class HandlerThermostatController extends HandlerBase {
// Logger
private final Logger logger = LoggerFactory.getLogger(HandlerThermostatController.class);
// Interface
public static final String INTERFACE = "Alexa.ThermostatController";
// Channel definitions
private static final ChannelInfo TARGET_SETPOINT = new ChannelInfo("targetSetpoint" /* propertyName */ ,
"targetSetpoint" /* ChannelId */, CHANNEL_TYPE_TARGETSETPOINT /* Channel 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) {
super(smartHomeDeviceHandler);
@ -63,17 +77,27 @@ public class HandlerThermostatController extends HandlerBase {
if (TARGET_SETPOINT.propertyName.equals(property)) {
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;
}
@Override
public void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result) {
QuantityType<Temperature> temperatureValue = null;
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())) {
JsonObject value = state.get("value").getAsJsonObject();
// 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)) {
@ -82,9 +106,43 @@ public class HandlerThermostatController extends HandlerBase {
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
@ -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;
}

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.loop.label = 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.description = Media length
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.radioStationId.label = TuneIn Radio Station Id
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.description = Speak the reminder and send a notification to the Alexa app
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.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.thermostatMode.label = Thermostat Mode
channel-type.amazonechocontrol.thermostatMode.description = Thermostat Mode
channel-type.amazonechocontrol.title.label = 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.description = Volume of the sound
channel-type.amazonechocontrol.waterAlarm.label = Water Alarm

View File

@ -634,6 +634,18 @@
<description>Temperature</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</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 -->
<channel-type id="targetSetpoint">
<item-type>Number:Temperature</item-type>
@ -641,4 +653,32 @@
<description>Target Setpoint</description>
<state pattern="%.1f %unit%"/>
</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>