diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java index ad3ef7b7396f9..a4c32e59db2ab 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java @@ -76,19 +76,20 @@ public class AccountServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(AccountServlet.class); - HttpService httpService; + final HttpService httpService; String servletUrlWithoutRoot; - String servletUrl; + final String servletUrl; AccountHandler account; String id; @Nullable Connection connectionToInitialize; - Gson gson = new Gson(); + final Gson gson; - public AccountServlet(HttpService httpService, String id, AccountHandler account) { + public AccountServlet(HttpService httpService, String id, AccountHandler account, Gson gson) { this.httpService = httpService; this.account = account; this.id = id; + this.gson = gson; try { servletUrlWithoutRoot = "amazonechocontrol/" + URLEncoder.encode(id, "UTF8"); } catch (UnsupportedEncodingException e) { @@ -112,7 +113,7 @@ private Connection reCreateConnection() { if (oldConnection == null) { oldConnection = account.findConnection(); } - return new Connection(oldConnection); + return new Connection(oldConnection, this.gson); } public void dispose() { @@ -157,7 +158,7 @@ void doVerb(String verb, @Nullable HttpServletRequest req, @Nullable HttpServlet Map map = req.getParameterMap(); String domain = map.get("domain")[0]; String loginData = connection.serializeLoginData(); - Connection newConnection = new Connection(null); + Connection newConnection = new Connection(null, this.gson); if (newConnection.tryRestoreLogin(loginData, domain)) { account.setConnection(newConnection); } @@ -282,7 +283,7 @@ protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResp } // handle commands if (baseUrl.equals("/newdevice") || baseUrl.equals("/newdevice/")) { - this.connectionToInitialize = new Connection(null); + this.connectionToInitialize = new Connection(null, this.gson); this.account.setConnection(null); resp.sendRedirect(this.servletUrl); return; @@ -301,9 +302,7 @@ protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResp Device device = account.findDeviceJson(serialNumber); if (device != null) { Thing thing = account.findThingBySerialNumber(device.serialNumber); - if (thing != null) { - handleIds(resp, connection, device, thing); - } + handleIds(resp, connection, device, thing); return; } } @@ -331,7 +330,7 @@ protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResp } public Map getQueryMap(@Nullable String query) { - Map map = new HashMap(); + Map map = new HashMap<>(); if (query != null) { String[] params = query.split("&"); for (String param : params) { @@ -374,6 +373,12 @@ private void handleDefaultPageResult(HttpServletResponse resp, String message, C html.append(" | "); html.append(StringEscapeUtils.escapeHtml("Logout and create new device id")); html.append(""); + // customer id + html.append("
Customer Id: "); + html.append(StringEscapeUtils.escapeHtml(connection.getCustomerId())); + // customer name + html.append("
Customer Name: "); + html.append(StringEscapeUtils.escapeHtml(connection.getCustomerName())); // device name html.append("
App name: "); html.append(StringEscapeUtils.escapeHtml(connection.getDeviceName())); @@ -393,7 +398,7 @@ private void handleDefaultPageResult(HttpServletResponse resp, String message, C // device list html.append( - ""); + "
DeviceSerial NumberStateThingFamilyType
"); for (Device device : this.account.getLastKnownDevices()) { html.append(""); + html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceOwnerCustomerId))); + html.append(""); + html.append(""); } html.append("
DeviceSerial NumberStateThingFamilyTypeCustomer Id
"); @@ -409,14 +414,18 @@ private void handleDefaultPageResult(HttpServletResponse resp, String message, C + URLEncoder.encode(device.serialNumber, "UTF8") + "'>" + StringEscapeUtils.escapeHtml(accountHandler.getLabel()) + ""); } else { - html.append("Not defined"); + html.append("" + + StringEscapeUtils.escapeHtml("Not defined") + ""); } html.append(""); html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceFamily))); html.append(""); html.append(StringEscapeUtils.escapeHtml(nullReplacement(device.deviceType))); html.append(""); - html.append("
"); createPageEndAndSent(resp, html); @@ -467,18 +476,36 @@ private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) } } - private void handleIds(HttpServletResponse resp, Connection connection, Device device, Thing thing) + private void handleIds(HttpServletResponse resp, Connection connection, Device device, @Nullable Thing thing) throws IOException, URISyntaxException { - StringBuilder html = createPageStart("Channel Options - " + thing.getLabel()); - + StringBuilder html; + if (thing != null) { + html = createPageStart("Channel Options - " + thing.getLabel()); + } else { + html = createPageStart("Device Information - No thing defined"); + } renderBluetoothMacChannel(connection, device, html); renderAmazonMusicPlaylistIdChannel(connection, device, html); renderPlayAlarmSoundChannel(connection, device, html); renderMusicProviderIdChannel(connection, html); - + renderCapabilities(connection, device, html); createPageEndAndSent(resp, html); } + private void renderCapabilities(Connection connection, Device device, StringBuilder html) { + html.append("

Capabilities

"); + html.append(""); + String[] capabilities = device.capabilities; + if (capabilities != null) { + for (String capability : capabilities) { + html.append(""); + } + } + html.append("
Name
"); + html.append(StringEscapeUtils.escapeHtml(capability)); + html.append("
"); + } + private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) { html.append("

" + StringEscapeUtils.escapeHtml("Channel " + CHANNEL_MUSIC_PROVIDER_ID) + "

"); html.append(""); @@ -599,11 +626,11 @@ void handleProxyRequest(Connection connection, HttpServletResponse resp, String try { Map headers = null; if (referer != null) { - headers = new HashMap(); + headers = new HashMap<>(); headers.put("Referer", referer); } - urlConnection = connection.makeRequest(verb, url, postData, json, false, headers); + urlConnection = connection.makeRequest(verb, url, postData, json, false, headers, 0); if (urlConnection.getResponseCode() == 302) { { String location = urlConnection.getHeaderField("location"); diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java index 258e7ba07ba59..45675aadba7a1 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java @@ -42,7 +42,7 @@ public class AmazonEchoControlBindingConstants { public static final ThingTypeUID THING_TYPE_FLASH_BRIEFING_PROFILE = new ThingTypeUID(BINDING_ID, "flashbriefingprofile"); - public static final Set SUPPORTED_THING_TYPES_UIDS = new HashSet( + public static final Set SUPPORTED_THING_TYPES_UIDS = new HashSet<>( Arrays.asList(THING_TYPE_ACCOUNT, THING_TYPE_ECHO, THING_TYPE_ECHO_SPOT, THING_TYPE_ECHO_SHOW, THING_TYPE_ECHO_WHA, THING_TYPE_FLASH_BRIEFING_PROFILE)); diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java index 4b7722e52ae07..f0cf1faf5e142 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java @@ -42,6 +42,8 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.http.HttpService; +import com.google.gson.Gson; + /** * The {@link AmazonEchoControlHandlerFactory} is responsible for creating things and thing * handlers. @@ -60,7 +62,9 @@ public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory { StorageService storageService; @Nullable BindingServlet bindingServlet; - + @Nullable + Gson gson; + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -97,11 +101,17 @@ protected void deactivate(ComponentContext componentContext) { if (storageService == null) { return null; } + Gson gson = this.gson; + if (gson == null) + { + gson = new Gson(); + this.gson = gson; + } if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { Storage storage = storageService.getStorage(thing.getUID().toString(), String.class.getClassLoader()); - AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage); + AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage, gson); registerDiscoveryService(bridgeHandler); BindingServlet bindingServlet = this.bindingServlet; if (bindingServlet != null) { @@ -115,7 +125,7 @@ protected void deactivate(ComponentContext componentContext) { return new FlashBriefingProfileHandler(thing, storage); } if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { - return new EchoHandler(thing); + return new EchoHandler(thing, gson); } return null; } @@ -124,7 +134,7 @@ private synchronized void registerDiscoveryService(AccountHandler bridgeHandler) AmazonEchoDiscovery discoveryService = new AmazonEchoDiscovery(bridgeHandler); discoveryService.activate(); this.discoveryServiceRegistrations.put(bridgeHandler.getThing().getUID(), bundleContext - .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable())); + .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); } @Override diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/BindingServlet.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/BindingServlet.java index bcefee5a83cde..112cd13a9a79b 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/BindingServlet.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/BindingServlet.java @@ -49,7 +49,7 @@ public class BindingServlet extends HttpServlet { String servletUrl; HttpService httpService; - List accountHandlers = new ArrayList(); + List accountHandlers = new ArrayList<>(); public BindingServlet(HttpService httpService) { this.httpService = httpService; @@ -126,5 +126,4 @@ protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResp logger.warn("return html failed with uri syntax error {}", e); } } - } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java index 37a4c3eda30cf..1b9b20d2ee80d 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java @@ -31,12 +31,13 @@ import java.util.Base64; import java.util.Date; import java.util.HashMap; -import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Scanner; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; @@ -46,6 +47,7 @@ import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.common.ThreadPoolManager; import org.eclipse.smarthome.core.util.HexUtils; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonActivities; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonActivities.Activity; @@ -58,6 +60,8 @@ import org.openhab.binding.amazonechocontrol.internal.jsons.JsonAutomation.Payload; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonAutomation.Trigger; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBootstrapResult; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBootstrapResult.Authentication; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState.DeviceNotificationState; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices; @@ -110,9 +114,15 @@ */ @NonNullByDefault public class Connection { + + private static final String THING_THREADPOOL_NAME = "thingHandler"; + + protected final ScheduledExecutorService scheduler = ThreadPoolManager + .getScheduledPool(THING_THREADPOOL_NAME); + private static final long expiresIn = 432000; // five days private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); - + private final Logger logger = LoggerFactory.getLogger(Connection.class); private final CookieManager cookieManager = new CookieManager(); @@ -129,11 +139,13 @@ public class Connection { private long renewTime = 0; private @Nullable String deviceName; private @Nullable String accountCustomerId; + private @Nullable String customerName; - private final Gson gson = new Gson(); + private final Gson gson; private final Gson gsonWithNullSerialization; - public Connection(@Nullable Connection oldConnection) { + public Connection(@Nullable Connection oldConnection, Gson gson) { + this.gson = gson; String frc = null; String serial = null; String deviceId = null; @@ -235,13 +247,29 @@ public String getDeviceName() { return deviceName; } + public String getCustomerId() { + String customerId = this.accountCustomerId; + if (customerId == null) { + return "Unknown"; + } + return customerId; + } + + public String getCustomerName() { + String customerName = this.customerName; + if (customerName == null) { + return "Unknown"; + } + return customerName; + } + public String serializeLoginData() { Date loginTime = this.loginTime; if (refreshToken == null || loginTime == null) { return ""; } StringBuilder builder = new StringBuilder(); - builder.append("6\n"); // version + builder.append("7\n"); // version builder.append(frc); builder.append("\n"); builder.append(serial); @@ -322,8 +350,8 @@ public boolean tryRestoreLogin(@Nullable String data, @Nullable String overloade } Scanner scanner = new Scanner(data); String version = scanner.nextLine(); - // check if serialize version - if (!version.equals("5") && !version.equals("6")) { + // check if serialize version is supported + if (!"5".equals(version) && !"6".equals(version) && !"7".equals(version)) { scanner.close(); return null; } @@ -345,8 +373,12 @@ public boolean tryRestoreLogin(@Nullable String data, @Nullable String overloade if (intVersion > 5) { String accountCustomerId = scanner.nextLine(); - if (!StringUtils.equals(accountCustomerId, "null")) { - this.accountCustomerId = accountCustomerId; + // Note: version 5 have wrong customer id serialized. + // Only use it, if it at least version 6 of serialization + if (intVersion > 6) { + if (!StringUtils.equals(accountCustomerId, "null")) { + this.accountCustomerId = accountCustomerId; + } } } @@ -403,6 +435,30 @@ public boolean tryRestoreLogin(@Nullable String data, @Nullable String overloade return loginTime; } + private @Nullable Authentication tryGetBootstrap() throws IOException, URISyntaxException { + HttpsURLConnection connection = makeRequest("GET", alexaServer + "/api/bootstrap", null, false, false, null, 0); + String contentType = connection.getContentType(); + if (connection.getResponseCode() == 200 && StringUtils.startsWithIgnoreCase(contentType, "application/json")) { + try { + String bootstrapResultJson = convertStream(connection); + JsonBootstrapResult result = parseJson(bootstrapResultJson, JsonBootstrapResult.class); + Authentication authentication = result.authentication; + if (authentication != null && authentication.authenticated) { + this.customerName = authentication.customerName; + if (this.accountCustomerId == null) { + this.accountCustomerId = authentication.customerId; + } + return authentication; + } + } + catch (JsonSyntaxException | IllegalStateException e) { + logger.info("No valid json received {}", e); + return null; + } + } + return null; + } + public String convertStream(HttpsURLConnection connection) throws IOException { InputStream input = connection.getInputStream(); if (input == null) { @@ -443,15 +499,20 @@ public String makeRequestAndReturnString(String url) throws IOException, URISynt public String makeRequestAndReturnString(String verb, String url, @Nullable String postData, boolean json, @Nullable Map customHeaders) throws IOException, URISyntaxException { - HttpsURLConnection connection = makeRequest(verb, url, postData, json, true, customHeaders); - return convertStream(connection); + HttpsURLConnection connection = makeRequest(verb, url, postData, json, true, customHeaders, 0); + String result = convertStream(connection); + this.logger.debug("Result of {} {}:{}", verb, url, result); + return result; } public HttpsURLConnection makeRequest(String verb, String url, @Nullable String postData, boolean json, - boolean autoredirect, @Nullable Map customHeaders) throws IOException, URISyntaxException { + boolean autoredirect, @Nullable Map customHeaders, int badRequestRepeats) + throws IOException, URISyntaxException { String currentUrl = url; - for (int i = 0; i < 30; i++) // loop for handling redirect, using automatic redirect is not possible, because - // all response headers must be catched + int redirectCounter = 0; + while (true) // loop for handling redirect and bad request, using automatic redirect is not possible, + // because + // all response headers must be catched { int code; HttpsURLConnection connection = null; @@ -559,18 +620,36 @@ public HttpsURLConnection makeRequest(String verb, String url, @Nullable String } } } + if (code == 400 && badRequestRepeats > 0) { + scheduler.schedule(() -> { + logger.debug("Retry call to {}", url); + try { + makeRequest(verb, url, postData, json, + autoredirect, customHeaders, badRequestRepeats - 1); + } catch (IOException | URISyntaxException e) { + logger.debug("Repeat fails {}", e); + } + }, 500, TimeUnit.MILLISECONDS); + return connection; + } if (code == 200) { logger.debug("Call to {} succeeded", url); return connection; } if (code == 302 && location != null) { logger.debug("Redirected to {}", location); + redirectCounter++; + if (redirectCounter > 30) { + throw new ConnectionException("Too many redirects"); + } currentUrl = location; if (autoredirect) { - continue; + continue; // repeat with new location } return connection; } + throw new HttpException(code, verb + " url '" + url + "' failed: " + connection.getResponseMessage()); + } catch (IOException e) { if (connection != null) { @@ -584,18 +663,14 @@ public HttpsURLConnection makeRequest(String verb, String url, @Nullable String } throw e; } - if (code != 200) { - throw new HttpException(code, verb + " url '" + url + "' failed: " + connection.getResponseMessage()); - } } - throw new ConnectionException("Too many redirects"); } public String registerConnectionAsApp(String oAutRedirectUrl) throws ConnectionException, IOException, URISyntaxException { URI oAutRedirectUri = new URI(oAutRedirectUrl); - Map queryParameters = new LinkedHashMap(); + Map queryParameters = new LinkedHashMap<>(); String query = oAutRedirectUri.getQuery(); String[] pairs = query.split("&"); for (String pair : pairs) { @@ -605,9 +680,9 @@ public String registerConnectionAsApp(String oAutRedirectUrl) } String accessToken = queryParameters.get("openid.oa2.access_token"); - Map cookieMap = new HashMap(); + Map cookieMap = new HashMap<>(); - List webSiteCookies = new ArrayList(); + List webSiteCookies = new ArrayList<>(); for (HttpCookie cookie : getSessionCookies("https://www.amazon.com")) { cookieMap.put(cookie.getName(), cookie.getValue()); webSiteCookies.add(new JsonWebSiteCookie(cookie.getName(), cookie.getValue())); @@ -620,7 +695,7 @@ public String registerConnectionAsApp(String oAutRedirectUrl) webSiteCookiesArray); String registerAppRequestJson = gson.toJson(registerAppRequest); - HashMap registerHeaders = new HashMap(); + HashMap registerHeaders = new HashMap<>(); registerHeaders.put("x-amzn-identity-auth-domain", "api.amazon.com"); String registerAppResultJson = makeRequestAndReturnString("POST", "https://api.amazon.com/auth/register", @@ -657,6 +732,7 @@ public String registerConnectionAsApp(String oAutRedirectUrl) setAmazonSite(host); try { exhangeToken(); + tryGetBootstrap(); } catch (Exception e) { logout(); throw e; @@ -683,11 +759,11 @@ private void exhangeToken() throws IOException, URISyntaxException { String cookiesBase64 = Base64.getEncoder().encodeToString(cookiesJson.getBytes()); String exchangePostData = "di.os.name=iOS&app_version=2.2.223830.0&domain=." + getAmazonSite() - + "&source_token=" + URLEncoder.encode(this.refreshToken, "UTF8") - + "&requested_token_type=auth_cookies&source_token_type=refresh_token&di.hw.version=iPhone&di.sdk.version=6.10.0&cookies=" - + cookiesBase64 + "&app_name=Amazon%20Alexa&di.os.version=11.4.1"; + + "&source_token=" + URLEncoder.encode(this.refreshToken, "UTF8") + + "&requested_token_type=auth_cookies&source_token_type=refresh_token&di.hw.version=iPhone&di.sdk.version=6.10.0&cookies=" + + cookiesBase64 + "&app_name=Amazon%20Alexa&di.os.version=11.4.1"; - HashMap exchangeTokenHeader = new HashMap(); + HashMap exchangeTokenHeader = new HashMap<>(); exchangeTokenHeader.put("Cookie", ""); String exchangeTokenJson = makeRequestAndReturnString("POST", @@ -770,22 +846,22 @@ public boolean verifyLogin() throws IOException, URISyntaxException { if (this.refreshToken == null) { return false; } - String response = makeRequestAndReturnString(alexaServer + "/api/bootstrap?version=0"); - Boolean result = response.contains("\"authenticated\":true"); - if (result) { + Authentication authentication = tryGetBootstrap(); + if (authentication != null && authentication.authenticated) { verifyTime = new Date(); if (loginTime == null) { loginTime = verifyTime; } + return true; } - return result; + return false; } public List getSessionCookies() { try { return cookieManager.getCookieStore().get(new URI(alexaServer)); } catch (URISyntaxException e) { - return new ArrayList(); + return new ArrayList<>(); } } @@ -793,7 +869,7 @@ public List getSessionCookies(String server) { try { return cookieManager.getCookieStore().get(new URI(server)); } catch (URISyntaxException e) { - return new ArrayList(); + return new ArrayList<>(); } } @@ -807,7 +883,7 @@ public void logout() { } // parser - private T parseJson(String json, Class type) throws JsonSyntaxException { + private T parseJson(String json, Class type) throws JsonSyntaxException, IllegalStateException { try { return gson.fromJson(json, type); } catch (JsonParseException | IllegalStateException e) { @@ -903,7 +979,7 @@ public JsonPlaylists getPlaylists(Device device) throws IOException, URISyntaxEx public void command(Device device, String command) throws IOException, URISyntaxException { String url = alexaServer + "/api/np/command?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType; - makeRequest("POST", url, command, true, true, null); + makeRequest("POST", url, command, true, true, null, 0); } public void notificationVolume(Device device, int volume) throws IOException, URISyntaxException { @@ -911,7 +987,7 @@ public void notificationVolume(Device device, int volume) throws IOException, UR + "/" + device.serialNumber; String command = "{\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType + "\",\"softwareVersion\":\"" + device.softwareVersion + "\",\"volumeLevel\":" + volume + "}"; - makeRequest("PUT", url, command, true, true, null); + makeRequest("PUT", url, command, true, true, null, 0); } public void ascendingAlarm(Device device, boolean ascendingAlarm) throws IOException, URISyntaxException { @@ -919,7 +995,7 @@ public void ascendingAlarm(Device device, boolean ascendingAlarm) throws IOExcep String command = "{\"ascendingAlarmEnabled\":" + (ascendingAlarm ? "true" : "false") + ",\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType + "\",\"deviceAccountId\":null}"; - makeRequest("PUT", url, command, true, true, null); + makeRequest("PUT", url, command, true, true, null, 0); } public DeviceNotificationState[] getDeviceNotificationStates() { @@ -957,11 +1033,11 @@ public void bluetooth(Device device, @Nullable String address) throws IOExceptio // disconnect makeRequest("POST", alexaServer + "/api/bluetooth/disconnect-sink/" + device.deviceType + "/" + device.serialNumber, "", - true, true, null); + true, true, null, 0); } else { makeRequest("POST", alexaServer + "/api/bluetooth/pair-sink/" + device.deviceType + "/" + device.serialNumber, - "{\"bluetoothDeviceAddress\":\"" + address + "\"}", true, true, null); + "{\"bluetoothDeviceAddress\":\"" + address + "\"}", true, true, null, 0); } } @@ -971,11 +1047,11 @@ public void playRadio(Device device, @Nullable String stationId) throws IOExcept } else { makeRequest("POST", alexaServer + "/api/tunein/queue-and-play?deviceSerialNumber=" + device.serialNumber - + "&deviceType=" + device.deviceType + "&guideId=" + stationId - + "&contentType=station&callSign=&mediaOwnerCustomerId=" - + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId - : this.accountCustomerId), - "", true, true, null); + + "&deviceType=" + device.deviceType + "&guideId=" + stationId + + "&contentType=station&callSign=&mediaOwnerCustomerId=" + + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId + : this.accountCustomerId), + "", true, true, null, 0); } } @@ -986,11 +1062,11 @@ public void playAmazonMusicTrack(Device device, @Nullable String trackId) throws String command = "{\"trackId\":\"" + trackId + "\",\"playQueuePrime\":true}"; makeRequest("POST", alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber - + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" - + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId - : this.accountCustomerId) - + "&shuffle=false", - command, true, true, null); + + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" + + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId + : this.accountCustomerId) + + "&shuffle=false", + command, true, true, null, 0); } } @@ -1002,17 +1078,17 @@ public void playAmazonMusicPlayList(Device device, @Nullable String playListId) String command = "{\"playlistId\":\"" + playListId + "\",\"playQueuePrime\":true}"; makeRequest("POST", alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber - + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" - + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId - : this.accountCustomerId) - + "&shuffle=false", - command, true, true, null); + + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" + + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId + : this.accountCustomerId) + + "&shuffle=false", + command, true, true, null, 0); } } public void sendNotificationToMobileApp(String customerId, String text, @Nullable String title) throws IOException, URISyntaxException { - Map parameters = new Hashtable(); + Map parameters = new HashMap<>(); parameters.put("notificationMessage", text); parameters.put("alexaUrl", "#v2/behaviors"); if (title != null && !StringUtils.isEmpty(title)) { @@ -1024,9 +1100,9 @@ public void sendNotificationToMobileApp(String customerId, String text, @Nullabl executeSequenceCommand(null, "Alexa.Notifications.SendMobilePush", parameters); } - public void sendAnnouncement(Device device, String text, @Nullable String title, int ttsVolume, int standardVolume) - throws IOException, URISyntaxException { - Map parameters = new Hashtable(); + public void sendAnnouncement(Device device, String text, @Nullable String bodyText, @Nullable String title, + int ttsVolume, int standardVolume) throws IOException, URISyntaxException { + Map parameters = new HashMap<>(); parameters.put("expireAfter", "PT5S"); JsonAnnouncementContent[] contentArray = new JsonAnnouncementContent[1]; JsonAnnouncementContent content = new JsonAnnouncementContent(); @@ -1070,19 +1146,19 @@ public void sendAnnouncement(Device device, String text, @Nullable String title, public void textToSpeech(Device device, String text, int ttsVolume, int standardVolume) throws IOException, URISyntaxException { - Map parameters = new Hashtable<>(); + Map parameters = new HashMap<>(); parameters.put("textToSpeak", text); executeSequenceCommandWithVolume(device, "Alexa.Speak", parameters, ttsVolume, standardVolume); } private void executeSequenceCommandWithVolume(@Nullable Device device, String command, @Nullable Map parameters, int ttsVolume, int standardVolume) - throws IOException, URISyntaxException { + throws IOException, URISyntaxException { if (ttsVolume != 0) { JsonArray nodesToExecute = new JsonArray(); - Map volumeParameters = new Hashtable<>(); + Map volumeParameters = new HashMap<>(); // add tts volume volumeParameters.clear(); volumeParameters.put("value", ttsVolume); @@ -1120,10 +1196,10 @@ private void executeSequenceNode(JsonObject nodeToExecute) throws IOException, U request.sequenceJson = gson.toJson(sequenceJson); String json = gson.toJson(request); - Map headers = new HashMap(); + Map headers = new HashMap<>(); headers.put("Routines-Version", "1.1.218665"); - makeRequest("POST", alexaServer + "/api/behaviors/preview", json, true, true, null); + makeRequest("POST", alexaServer + "/api/behaviors/preview", json, true, true, null, 3); } private void executeSequenceNodes(JsonArray nodesToExecute) throws IOException, URISyntaxException { @@ -1231,7 +1307,7 @@ public void startRoutine(Device device, String utterance) throws IOException, UR request.sequenceJson = sequenceJson; String requestJson = gson.toJson(request); - makeRequest("POST", alexaServer + "/api/behaviors/preview", requestJson, true, true, null); + makeRequest("POST", alexaServer + "/api/behaviors/preview", requestJson, true, true, null, 3); } else { logger.warn("Routine {} not found", utterance); } @@ -1257,7 +1333,7 @@ public void setEnabledFlashBriefings(JsonFeed[] enabledFlashBriefing) throws IOE JsonEnabledFeeds enabled = new JsonEnabledFeeds(); enabled.enabledFeeds = enabledFlashBriefing; String json = gsonWithNullSerialization.toJson(enabled); - makeRequest("POST", alexaServer + "/api/content-skills/enabled-feeds", json, true, true, null); + makeRequest("POST", alexaServer + "/api/content-skills/enabled-feeds", json, true, true, null, 0); } public JsonNotificationSound[] getNotificationSounds(Device device) throws IOException, URISyntaxException { @@ -1287,7 +1363,7 @@ public JsonNotificationResponse notification(Device device, String type, @Nullab Date date = new Date(new Date().getTime()); long createdDate = date.getTime(); Date alarm = new Date(createdDate + 5000); // add 5 seconds, because amazon does not except calls for times in - // the past (compared with the server time) + // the past (compared with the server time) long alarmTime = alarm.getTime(); JsonNotificationRequest request = new JsonNotificationRequest(); @@ -1325,7 +1401,7 @@ public JsonNotificationResponse getNotificationState(JsonNotificationResponse no public List getMusicProviders() { String response; try { - Map headers = new HashMap(); + Map headers = new HashMap<>(); headers.put("Routines-Version", "1.1.218665"); response = makeRequestAndReturnString("GET", alexaServer + "/api/behaviors/entities?skillId=amzn1.ask.1p.music", null, true, headers); @@ -1387,7 +1463,7 @@ public void playMusicVoiceCommand(Device device, String providerId, String voice startRoutineRequest.status = null; String postData = gson.toJson(startRoutineRequest); - makeRequest("POST", alexaServer + "/api/behaviors/preview", postData, true, true, null); + makeRequest("POST", alexaServer + "/api/behaviors/preview", postData, true, true, null, 3); } public JsonEqualizer getEqualizer(Device device) throws IOException, URISyntaxException { @@ -1399,6 +1475,6 @@ public JsonEqualizer getEqualizer(Device device) throws IOException, URISyntaxEx public void SetEqualizer(Device device, JsonEqualizer settings) throws IOException, URISyntaxException { String postData = gson.toJson(settings); makeRequest("POST", alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType, postData, - true, true, null); - } + true, true, null, 0); + } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java index 76656d246ea5d..302e7c90dcbaa 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java @@ -82,7 +82,7 @@ public WebSocketConnection(String amazonSite, List sessionCookies, } String deviceSerial = ""; - List cookiesForWs = new ArrayList(); + List cookiesForWs = new ArrayList<>(); for (HttpCookie cookie : sessionCookies) { if (cookie.getName().equals("ubid-acbde")) { deviceSerial = cookie.getValue(); diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java new file mode 100644 index 0000000000000..cd288e6dc3d46 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2019 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.channelhandler; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link ChannelHandler} is the base class for all channel handlers + * + * @author Michael Geramb - Initial contribution + */ +public abstract class ChannelHandler { + public abstract boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command) + throws IOException, URISyntaxException; + + protected final IAmazonThingHandler thingHandler; + protected final Gson gson; + private final Logger logger; + + protected ChannelHandler(IAmazonThingHandler thingHandler, Gson gson) { + this.logger = LoggerFactory.getLogger(this.getClass()); + this.thingHandler = thingHandler; + this.gson = gson; + } + + protected @Nullable T tryParseJson(String json, Class type) { + try { + return gson.fromJson(json, type); + } catch (JsonSyntaxException e) { + logger.debug("Json parse error {}", e); + return null; + } + } + + protected @Nullable T parseJson(String json, Class type) throws JsonSyntaxException { + return gson.fromJson(json, type); + + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java new file mode 100644 index 0000000000000..26425b44b21aa --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2019 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.channelhandler; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +/** + * The {@link ChannelHandlerAnnouncement} is responsible for the announcement channel + * + * @author Michael Geramb - Initial contribution + */ +public class ChannelHandlerAnnouncement extends ChannelHandler { + public static final String CHANNEL_NAME = "announcement"; + + public ChannelHandlerAnnouncement(IAmazonThingHandler thingHandler, Gson gson) { + super(thingHandler, gson); + } + + @Override + public boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command) + throws IOException, URISyntaxException { + if (channelId.equals(CHANNEL_NAME)) { + if (command instanceof StringType) { + String commandValue = ((StringType) command).toFullString(); + String body = commandValue; + String title = null; + String speak = " "; // blank generates a beep + if (commandValue.startsWith("{") && commandValue.endsWith("}")) { + try { + AnnouncementRequestJson request = parseJson(commandValue, AnnouncementRequestJson.class); + if (request != null) { + title = request.title; + body = request.body; + if (body == null) { + body = ""; + } + if (request.sound == false) { + speak = ""; + } + } + } catch (JsonSyntaxException e) { + body = e.getLocalizedMessage(); + } + } + connection.sendAnnouncement(device, speak, body, title, 0, 0); + } + RefreshChannel(); + } + return false; + } + + void RefreshChannel() { + thingHandler.updateChannelState(CHANNEL_NAME, new StringType("")); + } + + class AnnouncementRequestJson { + public @Nullable Boolean sound; + public @Nullable String title; + public @Nullable String body; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/IAmazonThingHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/IAmazonThingHandler.java new file mode 100644 index 0000000000000..f0e412c9879ed --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/IAmazonThingHandler.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2019 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.channelhandler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.types.State; + +/** + * The {@link IAmazonThingHandler} is used from ChannelHandlers to communicate with the thing + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public interface IAmazonThingHandler { + void updateChannelState(String channelId, State state); +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java index 437961fb7890a..d5dd3ca0b46ca 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java @@ -15,8 +15,8 @@ import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; @@ -49,7 +49,7 @@ public class AmazonEchoDiscovery extends AbstractDiscoveryService implements Ext AccountHandler accountHandler; private final Logger logger = LoggerFactory.getLogger(AmazonEchoDiscovery.class); - private final HashSet discoverdFlashBriefings = new HashSet(); + private final HashSet discoverdFlashBriefings = new HashSet<>(); @Nullable ScheduledFuture startScanStateJob; @@ -68,7 +68,7 @@ public AmazonEchoDiscovery(AccountHandler accountHandler) { } public void activate() { - activate(new Hashtable()); + activate(new HashMap<>()); } @Override diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java index 3a3db9cd87cc5..871c56798e13b 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java @@ -95,11 +95,12 @@ public class AccountHandler extends BaseBridgeHandler implements IWebSocketComma private String currentFlashBriefingJson = ""; private final HttpService httpService; private @Nullable AccountServlet accountServlet; - private final Gson gson = new Gson(); + private final Gson gson; int checkDataCounter; - public AccountHandler(Bridge bridge, HttpService httpService, Storage stateStorage) { + public AccountHandler(Bridge bridge, HttpService httpService, Storage stateStorage, Gson gson) { super(bridge); + this.gson = gson; this.httpService = httpService; this.stateStorage = stateStorage; } @@ -111,11 +112,11 @@ public void initialize() { synchronized (synchronizeConnection) { Connection connection = this.connection; if (connection == null) { - this.connection = new Connection(null); + this.connection = new Connection(null, gson); } } if (this.accountServlet == null) { - this.accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this); + this.accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this, gson); } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Wait for login"); @@ -558,7 +559,7 @@ public List updateDeviceList() { Connection currentConnection = connection; if (currentConnection == null) { - return new ArrayList(); + return new ArrayList<>(); } List devices = null; @@ -600,7 +601,7 @@ public List updateDeviceList() { if (devices != null) { return devices; } - return new ArrayList(); + return new ArrayList<>(); } public void setEnabledFlashBriefingsJson(String flashBriefingJson) { diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java index 2bb4e8fb3ad03..42c0084d26994 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java @@ -20,8 +20,9 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Objects; @@ -55,10 +56,14 @@ import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; import org.openhab.binding.amazonechocontrol.internal.Connection; import org.openhab.binding.amazonechocontrol.internal.ConnectionException; import org.openhab.binding.amazonechocontrol.internal.HttpException; +import org.openhab.binding.amazonechocontrol.internal.channelhandler.ChannelHandler; +import org.openhab.binding.amazonechocontrol.internal.channelhandler.ChannelHandlerAnnouncement; +import org.openhab.binding.amazonechocontrol.internal.channelhandler.IAmazonThingHandler; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonActivities.Activity; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonActivities.Activity.Description; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonAscendingAlarm.AscendingAlarmModel; @@ -94,10 +99,10 @@ * @author Michael Geramb - Initial contribution */ @NonNullByDefault -public class EchoHandler extends BaseThingHandler { +public class EchoHandler extends BaseThingHandler implements IAmazonThingHandler { private final Logger logger = LoggerFactory.getLogger(EchoHandler.class); - private Gson gson = new Gson(); + private Gson gson; private @Nullable Device device; private Set capabilities = new HashSet<>(); private @Nullable AccountHandler account; @@ -128,6 +133,7 @@ public class EchoHandler extends BaseThingHandler { private @Nullable JsonPlaylists playLists; private @Nullable JsonNotificationSound @Nullable [] alarmSounds; private @Nullable List musicProviders; + private List channelHandlers = new ArrayList<>(); private @Nullable JsonNotificationResponse currentNotification; private @Nullable ScheduledFuture currentNotifcationUpdateTimer; @@ -136,8 +142,10 @@ public class EchoHandler extends BaseThingHandler { long mediaStartMs; String lastSpokenText = ""; - public EchoHandler(Thing thing) { + public EchoHandler(Thing thing, Gson gson) { super(thing); + this.gson = gson; + channelHandlers.add(new ChannelHandlerAnnouncement(this, this.gson)); } @Override @@ -263,8 +271,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } - // Player commands String channelId = channelUID.getId(); + for (ChannelHandler channelHandler : channelHandlers) { + if (channelHandler.tryHandleCommand(device, connection, channelId, command)) { + return; + } + } + + // Player commands if (channelId.equals(CHANNEL_PLAYER)) { if (command == PlayPauseType.PAUSE || command == OnOffType.OFF) { connection.command(device, "{\"type\":\"PauseCommand\"}"); @@ -375,7 +389,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { + ",\"contentFocusClientId\":\"Default\"}"); } else { - Map parameters = new Hashtable(); + Map parameters = new HashMap<>(); parameters.put("value", volume); connection.executeSequenceCommand(device, "Alexa.DeviceControls.Volume", parameters); } @@ -725,7 +739,7 @@ private void startTextToSpeech(Connection connection, Device device, String text this.ignoreVolumeChange = scheduler.schedule(this::stopIgnoreVolumeChange, 2000, TimeUnit.MILLISECONDS); } if (text.startsWith("") && text.endsWith("")) { - connection.sendAnnouncement(device, text, null, textToSpeechVolume, lastKnownVolume); + connection.sendAnnouncement(device, text, null, null, textToSpeechVolume, lastKnownVolume); } else { connection.textToSpeech(device, text, textToSpeechVolume, lastKnownVolume); } @@ -861,6 +875,13 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, if (StringUtils.startsWith(musicProviderId, "TUNEIN")) { musicProviderId = "TUNEIN"; } + if (StringUtils.startsWithIgnoreCase(musicProviderId, "iHeartRadio")) { + musicProviderId = "I_HEART_RADIO"; + } + if (StringUtils.containsIgnoreCase(musicProviderId, "Apple") + && StringUtils.containsIgnoreCase(musicProviderId, "Music")) { + musicProviderId = "APPLE_MUSIC"; + } } } progress = playerInfo.progress; @@ -1330,4 +1351,9 @@ public void updateNotifications(ZonedDateTime currentTime, ZonedDateTime now, nextMusicAlarm == null ? UnDefType.UNDEF : new DateTimeType(nextMusicAlarm)); updateState(CHANNEL_NEXT_TIMER, nextTimer == null ? UnDefType.UNDEF : new DateTimeType(nextTimer)); } + + @Override + public void updateChannelState(String channelId, State state) { + updateState(channelId, state); + } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java new file mode 100644 index 0000000000000..dffce100b83b8 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2019 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.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link JsonBluetoothStates} encapsulate the bootstrap result + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class JsonBootstrapResult { + + public @Nullable Authentication authentication; + + public static class Authentication { + public boolean authenticated; + public @Nullable Boolean canAccessPrimeMusicContent; + public @Nullable String customerEmail; + public @Nullable String customerId; + public @Nullable String customerName; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties index e3b6d0b14de0a..3c39840e2980e 100755 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties @@ -56,6 +56,9 @@ channel-type.amazonechocontrol.providerDisplayName.description = Name des Musika channel-type.amazonechocontrol.bluetoothMAC.label = Bluetooth Verbindung channel-type.amazonechocontrol.bluetoothMAC.description = MAC-Adresse des verbundenen Bluetoothgerätes +channel-type.amazonechocontrol.announcement.label = Ankündigung +channel-type.amazonechocontrol.announcement.description = Zeigt die Ankündungsnachricht am Display (Nur schreiben). In der Binding Beschreibung ist im Tutorials Abschnitt eine Erklärung wie der Title gesetzt und der Sound deaktiviert wird. + channel-type.amazonechocontrol.textToSpeech.label = Sprich channel-type.amazonechocontrol.textToSpeech.description = Spricht den Text (Nur schreiben). Es kann reiner Text oder SSML verwendet werden: e.g. Ich will dir ein Geheimnis erzählen.Ich bin nicht wirklich ein Mensch. diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml index 54b7acb0f7a42..f2447c3ec654f 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml @@ -1,8 +1,9 @@ + xmlns:thing="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + bindingId="amazonechocontrol" + xsi:schemaLocation="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0 http://eclipse.org/smarthome/schemas/thing-description-1.0.0.xsd"> Amazon Account where the amazon echo devices are registered. @@ -27,14 +28,17 @@ - + - + - + @@ -47,7 +51,8 @@ - + @@ -80,15 +85,19 @@ - + - + - + + @@ -100,7 +109,8 @@ - + @@ -133,15 +143,19 @@ - + - + - + + @@ -153,7 +167,8 @@ - + @@ -182,11 +197,13 @@ - + - + serialNumber @@ -267,7 +284,8 @@ Amazon Music play list id (Write only, no current state) - + String Id of the playlist which was started with openHAB @@ -365,6 +383,11 @@ Voice command as text. E.g. 'Yesterday from the Beatles' (Write only) + + String + + Display the announcement message on the display (Write only). See in the tutorial section of the binding description to learn how it's possible to set the title and turn off the sound. + String
NameValue