diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/README.md b/bundles/org.smarthomej.binding.amazonechocontrol/README.md index 50b7e2dc80..f83c413f6b 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/README.md +++ b/bundles/org.smarthomej.binding.amazonechocontrol/README.md @@ -4,12 +4,17 @@ This binding can control Amazon Echo devices (Alexa) and Smarthome devices conne Upgrade notice: -- If you upgrade from a version before 2.5.0 you need to reset the account and login again (see at the bottom). - The `lastVoiceCommand` channel of the `amazonechocontrol` binding changed its behavior in version 3.2.8. Due to a wrong implementation the channel changed it's state to an empty string if the same command was received again. This has been corrected. If you want to be notified about every state update, please adjust your rule triggers to "received update". -If you want to be notified about state changes (i.e. different commands), use `state changed`. +If you want to be notified about state changes (i.e. different commands), use "state changed". +- The write-only channels now use `autoUpdatePolicy=veto` (i.e. they don't update the item's state when a command was send). +- The channels `amazonMusic`, `amazonMusicTrackId`, `amazonPlaylistId`, `radio` and `radioStationId` have been removed because they are no longer supported from Amazon. +You can use the `textCommand` channel with a value of `Play playlist CrazyMusic on AmazonMusic` instead. +- The `lastVoiceCommand` channel will be converted to a read-only channel. +Using commands to that channel is deprecated and will stop working in future versions. +Please use the `textToSpeech` channel instead. ## What this can be used for @@ -105,11 +110,12 @@ The configuration of your Amazon account must be done in the 'Amazon Account' de ### Thing Configuration -| Configuration name | Default | Description | -|---------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------| -| `discoverSmartHome` | 0 | 0...No discover, 1...Discover direct connected, 2...Discover direct and Alexa skill devices, 3...Discover direct, Alexa and openHAB skill devices | -| `pollingIntervalSmartHomeAlexa` | 30 | Defines the time in seconds for openHAB to pull the state of the Alexa connected devices. The minimum is 10 seconds. | -| `pollingIntervalSmartSkills` | 120 | Defines the time in seconds for openHAB to pull the state of the over a skill connected devices. The minimum is 60 seconds. | +| Configuration name | Default | Description | +|---------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `discoverSmartHome` | 0 | 0...No discover, 1...Discover direct connected, 2...Discover direct and Alexa skill devices, 3...Discover direct, Alexa and openHAB skill devices | +| `pollingIntervalSmartHomeAlexa` | 30 | Defines the time in seconds for openHAB to pull the state of the Alexa connected devices. The minimum is 10 seconds. | +| `pollingIntervalSmartSkills` | 120 | Defines the time in seconds for openHAB to pull the state of the over a skill connected devices. The minimum is 60 seconds. | +| `activityRequestDelay` | 10 | The number of seconds between a voice command was detected and the received command is requested from the server. The minimum is 2 seconds. Lower values improve response time but may result in loss of events. | ### Channels @@ -136,57 +142,45 @@ You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/ ### Channels -| Channel Type ID | Item Type | Access Mode | Thing Type | Description | -|---------------------------|-------------|-------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| player | Player | R/W | echo, echoshow, echospot, wha | Control the music player (Supported commands: PLAY or ON, PAUSE or OFF, NEXT, PREVIOUS, REWIND, FASTFORWARD) | -| volume | Dimmer | R/W | echo, echoshow, echospot | Control the volume | -| equalizerTreble | Number | R/W | echo, echoshow, echospot | Control the treble (value from -6 to 6) | -| equalizerMidrange | Number | R/W | echo, echoshow, echospot | Control the midrange (value from -6 to 6) | -| equalizerBass | Number | R/W | echo, echoshow, echospot | Control the bass (value from -6 to 6) | -| shuffle | Switch | R/W | echo, echoshow, echospot, wha | Shuffle play if applicable, e.g. playing a playlist | -| imageUrl | String | R | echo, echoshow, echospot, wha | Url of the album image or radio station logo | -| title | String | R | echo, echoshow, echospot, wha | Title of the current media | -| subtitle1 | String | R | echo, echoshow, echospot, wha | Subtitle of the current media | -| subtitle2 | String | R | echo, echoshow, echospot, wha | Additional subtitle of the current media | -| providerDisplayName | String | R | echo, echoshow, echospot, wha | Name of the music provider | -| bluetoothMAC | String | R/W | echo, echoshow, echospot | Bluetooth device MAC. Used to connect to a specific device or disconnect if an empty string was provided | -| bluetooth | Switch | R/W | echo, echoshow, echospot | Connect/Disconnect to the last used bluetooth device (works after a bluetooth connection was established after the openHAB start) | -| bluetoothDeviceName | String | R | echo, echoshow, echospot | User friendly name of the connected bluetooth device | -| radioStationId | String | R/W | echo, echoshow, echospot, wha | Start playing of a TuneIn radio station by specifying its id or stops playing if an empty string was provided | -| radio | Switch | R/W | echo, echoshow, echospot, wha | Start playing of the last used TuneIn radio station (works after the radio station started after the openHAB start) | -| amazonMusicTrackId (*) | String | R/W | echo, echoshow, echospot, wha | Start playing of an Amazon Music track by its id or stops playing if an empty string was provided | -| amazonMusicPlayListId (*) | String | W | echo, echoshow, echospot, wha | Write Only! Start playing of an Amazon Music playlist by specifying its id or stops playing if an empty string was provided. | -| amazonMusic (*) | Switch | R/W | echo, echoshow, echospot, wha | Start playing of the last used Amazon Music song (works after at least one song was started after the openHAB start) | -| remind | String | R/W | echo, echoshow, echospot | Write Only! Speak the reminder and sends a notification to the Alexa app (Currently the reminder is played and notified two times, this seems to be a bug in the Amazon software) | -| nextReminder | DateTime | R | echo, echoshow, echospot | Next reminder on the device | -| playAlarmSound | String | W | echo, echoshow, echospot | Write Only! Plays an Alarm sound | -| nextAlarm | DateTime | R | echo, echoshow, echospot | Next alarm on the device | -| nextMusicAlarm | DateTime | R | echo, echoshow, echospot | Next music alarm on the device | -| nextTimer | DateTime | R | echo, echoshow, echospot | Next timer on the device | -| startRoutine | String | W | echo, echoshow, echospot | Write Only! Type in what you normally say to Alexa without the preceding "Alexa," | -| musicProviderId | String | R/W | echo, echoshow, echospot | Current Music provider | -| playMusicVoiceCommand | String | W | echo, echoshow, echospot | Write Only! Voice command as text. E.g. 'Yesterday from the Beatles' | -| startCommand | String | W | echo, echoshow, echospot | Write Only! Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing. (Note: The options are case sensitive) | -| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. See in the tutorial section to learn how it’s possible to set the title and turn off the sound. | -| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `I want to tell you a secret.I am not a real human.` | -| textToSpeechVolume | Dimmer | R/W | echo, echoshow, echospot | Volume of the textToSpeech channel, if 0 the current volume will be used | -| textCommand | String | W | echo, echoshow, echospot | Write Only! Execute a text command (like a spoken text) | -| lastVoiceCommand | String | R/W | echo, echoshow, echospot | Last voice command spoken to the device. Writing to the channel starts voice output. | -| lastSpokenText | String | R | echo, echoshow, echospot | Last spoken text from the device. (for example statements, answers and text to speeches) | -| mediaProgress | Dimmer | R/W | echo, echoshow, echospot | Media progress in percent | -| mediaProgressTime | Number:Time | R/W | echo, echoshow, echospot | Media play time | -| mediaLength | Number:Time | R | echo, echoshow, echospot | Media length | -| notificationVolume | Dimmer | R | echo, echoshow, echospot | Notification volume | -| ascendingAlarm | Switch | R/W | echo, echoshow, echospot | Ascending alarm up to the configured volume | -| doNotDisturb | Switch | R/W | echo, echoshow, echospot | Do Not Disturb mode enabled | -| sendMessage | String | W | account | Write Only! Sends a message to the Echo devices. | -| save | Switch | W | flashbriefingprofile | Write Only! Stores the current configuration of flash briefings within the thing | -| active | Switch | R/W | flashbriefingprofile | Active the profile | -| playOnDevice | String | W | flashbriefingprofile | Write Only! Specify the echo serial number or name to start the flash briefing. | - -**Attention:** Channels marked with (*) are deprecated and will be removed in the future. -Amazon already started to remove some of that functionality. -You can use the `textCommand` channel with a value of `Play playlist CrazyMusic on AmazonMusic` instead. +| Channel Type ID | Item Type | Access Mode | Thing Type | Description | +|-----------------------|-------------|-------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| player | Player | R/W | echo, echoshow, echospot, wha | Control the music player (Supported commands: PLAY or ON, PAUSE or OFF, NEXT, PREVIOUS, REWIND, FASTFORWARD) | +| volume | Dimmer | R/W | echo, echoshow, echospot | Control the volume | +| equalizerTreble | Number | R/W | echo, echoshow, echospot | Control the treble (value from -6 to 6) | +| equalizerMidrange | Number | R/W | echo, echoshow, echospot | Control the midrange (value from -6 to 6) | +| equalizerBass | Number | R/W | echo, echoshow, echospot | Control the bass (value from -6 to 6) | +| shuffle | Switch | R/W | echo, echoshow, echospot, wha | Shuffle play if applicable, e.g. playing a playlist | +| imageUrl | String | R | echo, echoshow, echospot, wha | Url of the album image or radio station logo | +| title | String | R | echo, echoshow, echospot, wha | Title of the current media | +| subtitle1 | String | R | echo, echoshow, echospot, wha | Subtitle of the current media | +| subtitle2 | String | R | echo, echoshow, echospot, wha | Additional subtitle of the current media | +| providerDisplayName | String | R | echo, echoshow, echospot, wha | Name of the music provider | +| bluetoothMAC | String | R/W | echo, echoshow, echospot | Bluetooth device MAC. Used to connect to a specific device or disconnect if an empty string was provided | +| bluetooth | Switch | R/W | echo, echoshow, echospot | Connect/Disconnect to the last used bluetooth device (works after a bluetooth connection was established after the openHAB start) | +| bluetoothDeviceName | String | R | echo, echoshow, echospot | User friendly name of the connected bluetooth device | +| radio | Switch | R/W | echo, echoshow, echospot, wha | Start playing of the last used TuneIn radio station (works after the radio station started after the openHAB start) | +| remind | String | W | echo, echoshow, echospot | Write Only! Speak the reminder and sends a notification to the Alexa app (Currently the reminder is played and notified two times, this seems to be a bug in the Amazon software) | +| nextReminder | DateTime | R | echo, echoshow, echospot | Next reminder on the device | +| playAlarmSound | String | W | echo, echoshow, echospot | Write Only! Plays an Alarm sound | +| nextAlarm | DateTime | R | echo, echoshow, echospot | Next alarm on the device | +| nextMusicAlarm | DateTime | R | echo, echoshow, echospot | Next music alarm on the device | +| nextTimer | DateTime | R | echo, echoshow, echospot | Next timer on the device | +| startRoutine | String | W | echo, echoshow, echospot | Write Only! Type in what you normally say to Alexa without the preceding "Alexa," | +| musicProviderId | String | R/W | echo, echoshow, echospot | Current Music provider | +| playMusicVoiceCommand | String | W | echo, echoshow, echospot | Write Only! Voice command as text. E.g. 'Yesterday from the Beatles' | +| startCommand | String | W | echo, echoshow, echospot | Write Only! Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing. (Note: The options are case sensitive) | +| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. See in the tutorial section to learn how it’s possible to set the title and turn off the sound. | +| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `I want to tell you a secret.I am not a real human.` | +| textToSpeechVolume | Dimmer | R/W | echo, echoshow, echospot | Volume of the textToSpeech channel, if 0 the current volume will be used | +| textCommand | String | W | echo, echoshow, echospot | Write Only! Execute a text command (like a spoken text) | +| lastVoiceCommand | String | R | echo, echoshow, echospot | Last voice command spoken to the device. | +| lastSpokenText | String | R | echo, echoshow, echospot | Last spoken text from the device. (for example statements, answers and text to speeches) | +| mediaProgress | Dimmer | R/W | echo, echoshow, echospot | Media progress in percent | +| mediaProgressTime | Number:Time | R/W | echo, echoshow, echospot | Media play time | +| mediaLength | Number:Time | R | echo, echoshow, echospot | Media length | +| notificationVolume | Dimmer | R | echo, echoshow, echospot | Notification volume | +| ascendingAlarm | Switch | R/W | echo, echoshow, echospot | Ascending alarm up to the configured volume | +| doNotDisturb | Switch | R/W | echo, echoshow, echospot | Do Not Disturb mode enabled | ## Advanced Feature Technically Experienced Users @@ -277,15 +271,6 @@ String Echo_Living_Room_PlayAlarmSound "Play Alarm Sound" String Echo_Living_Room_StartRoutine "Start Routine" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:startRoutine"} Dimmer Echo_Living_Room_NotificationVolume "Notification volume" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:notificationVolume"} Switch Echo_Living_Room_AscendingAlarm "Ascending alarm" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:ascendingAlarm"} - -// Flashbriefings -Switch FlashBriefing_Technical_Save "Save (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing1:save"} -Switch FlashBriefing_Technical_Active "Active" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing1:active"} -String FlashBriefing_Technical_Play "Play (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing1:playOnDevice"} - -Switch FlashBriefing_LifeStyle_Save "Save (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing2:save"} -Switch FlashBriefing_LifeStyle_Active "Active" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing2:active"} -String FlashBriefing_LifeStyle_Play "Play (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing2:playOnDevice"} ``` #### echo.sitemap: @@ -334,19 +319,38 @@ sitemap amazonechocontrol label="Echo Devices" } ``` -## Flash Briefing `flashbriefingprofile` +## Flash-Briefing `flashbriefingprofile` + +Flash briefings are sets of information that can be configured in your Alexa app. +The app only allows to have one flash-briefing configuration at the same time (e.g. weather and news). +The `flashbriefingprofile` thing helps you to overcome this limitation. + +To set it up using managed (UI) configuration: + +1. Use the app to create the flash-briefing configuration you want. +2. Start the discovery, you should see a new "flashbriefingprofile" thing. If this is not the case, a thing with the current configuration already exists. +3. Add that thing (you can use a custom name if you want). +4. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different). + +Textual configuration (untested, not recommended): + +1. Add a new `flashbriefiungprofilething` to your `.things` file. +2. Use the app to create the flash-briefing configuration you want. +3. Send `ON` to the `save` channel of the thing you created in step 1. +4. Repeat steps 1-3 for other configurations (you can add as many as you want, make sure they are different). ### Channels -The flashbriefingprofile thing has no configuration parameters. -It will be configured at runtime by using the save channel to store the current flash briefing configuration which is set in the alexa app in the thing. Create a flashbriefingprofile Thing for each set you need. -E.g. One Flashbriefing profile with technical news and weather, one for playing world news and one for sport news. +The `flashbriefingprofile` thing has no configuration parameters. + +| Channel Type ID | Item Type | Access Mode | Description | +|-----------------|-----------|:-----------:|-----------------------------------------------------------------------------------------------------------------------------------------------| +| `save` | Switch | W | Write Only! Stores the current configuration of flash briefings. | +| `active` | Switch | R/W | Activates this flash briefing as default ON ALL DEVICES. | +| `playOnDevice` | String | W | Specify the echo serial number or name to start the flash-briefing. This is only exceuted once and the default configuration does not change. | -| Channel Type ID | Item Type | Access Mode | Thing Type | Description | -|-----------------|-----------|-------------|----------------------|----------------------------------------------------------------------------------| -| save | Switch | W | flashbriefingprofile | Write Only! Stores the current configuration of flash briefings within the thing | -| active | Switch | R/W | flashbriefingprofile | Active the profile | -| playOnDevice | String | W | flashbriefingprofile | Specify the echo serial number or name to start the flash briefing. | +**Attention:** Be careful when using the `save` channel. +Storing the same configuration to several things may result in unpredictable behavior. ### Example @@ -362,9 +366,6 @@ Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discove #### flashbriefings.items: -Sample for the Thing echo1 only. But it will work in the same way for the other things, only replace the thing name in the channel link. -Take a look in the channel description above to know, which channels are supported by your thing type. - ``` // Flashbriefings Switch FlashBriefing_Technical_Save "Save (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing1:save"} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/pom.xml b/bundles/org.smarthomej.binding.amazonechocontrol/pom.xml index ab2c9bea47..625578171f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/pom.xml +++ b/bundles/org.smarthomej.binding.amazonechocontrol/pom.xml @@ -27,6 +27,18 @@ 1.1.6.RELEASE compile + + org.apache.velocity + velocity-engine-core + 2.3 + provided + + + org.eclipse.jdt + org.eclipse.jdt.annotation + 2.2.600 + compile + diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/feature/feature.xml b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/feature/feature.xml index 3378862513..9325f27109 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/feature/feature.xml +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/feature/feature.xml @@ -3,6 +3,7 @@ openhab-runtime-base mvn:org.smarthomej.addons.bundles/org.smarthomej.commons/${project.version} + mvn:org.apache.velocity/velocity-engine-core/2.3 mvn:org.smarthomej.addons.bundles/org.smarthomej.binding.amazonechocontrol/${project.version} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountHandlerConfig.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountHandlerConfig.java index 473c53a46e..cabdf179cb 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountHandlerConfig.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountHandlerConfig.java @@ -26,4 +26,5 @@ public class AccountHandlerConfig { public int discoverSmartHome = 0; public int pollingIntervalSmartHomeAlexa = 60; public int pollingIntervalSmartSkills = 120; + public int activityRequestDelay = 10; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountServlet.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountServlet.java deleted file mode 100644 index 3ff2e673ad..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AccountServlet.java +++ /dev/null @@ -1,711 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; - -import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import javax.net.ssl.HttpsURLConnection; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.Thing; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMusicProvider; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaylists; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList; -import org.unbescape.html.HtmlEscape; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -/** - * Provides the following functions - * --- Login --- - * Simple http proxy to forward the login dialog from amazon to the user through the binding - * so the user can enter a captcha or other extended login information - * --- List of devices --- - * Used to get the device information of new devices which are currently not known - * --- List of IDs --- - * Simple possibility for a user to get the ids needed for writing rules - * - * @author Michael Geramb - Initial Contribution - */ -@NonNullByDefault -public class AccountServlet extends HttpServlet { - - private static final long serialVersionUID = -1453738923337413163L; - private static final String FORWARD_URI_PART = "/FORWARD/"; - private static final String PROXY_URI_PART = "/PROXY/"; - - private final Logger logger = LoggerFactory.getLogger(AccountServlet.class); - - private final HttpService httpService; - private final String servletUrlWithoutRoot; - private final String servletUrl; - private final AccountHandler account; - private final String id; - private @Nullable Connection connectionToInitialize; - private final Gson gson; - - 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, StandardCharsets.UTF_8); - servletUrl = "/" + servletUrlWithoutRoot; - - httpService.registerServlet(servletUrl, this, null, httpService.createDefaultHttpContext()); - } catch (NamespaceException | ServletException e) { - throw new IllegalStateException(e.getMessage()); - } - } - - private Connection reCreateConnection() { - Connection oldConnection = connectionToInitialize; - if (oldConnection == null) { - oldConnection = account.findConnection(); - } - return new Connection(oldConnection, this.gson); - } - - public void dispose() { - httpService.unregister(servletUrl); - } - - @Override - protected void doPut(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) - throws ServletException, IOException { - doVerb("PUT", req, resp); - } - - @Override - protected void doDelete(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) - throws ServletException, IOException { - doVerb("DELETE", req, resp); - } - - @Override - protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) - throws ServletException, IOException { - doVerb("POST", req, resp); - } - - void doVerb(String verb, @Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException { - if (req == null) { - return; - } - if (resp == null) { - return; - } - String requestUri = req.getRequestURI(); - if (requestUri == null) { - return; - } - String baseUrl = requestUri.substring(servletUrl.length()); - String uri = baseUrl; - String queryString = req.getQueryString(); - if (queryString != null && queryString.length() > 0) { - uri += "?" + queryString; - } - - Connection connection = this.account.findConnection(); - if (connection != null && "/changedomain".equals(uri)) { - Map map = req.getParameterMap(); - String[] domainArray = map.get("domain"); - if (domainArray == null) { - logger.warn("Could not determine domain"); - return; - } - String domain = domainArray[0]; - String loginData = connection.getLoginData().serializeLoginData(); - Connection newConnection = new Connection(null, this.gson); - if (newConnection.tryRestoreLogin(loginData, domain)) { - account.setConnection(newConnection); - } - resp.sendRedirect(servletUrl); - return; - } - if (uri.startsWith(PROXY_URI_PART)) { - // handle proxy request - - if (connection == null) { - returnError(resp, "Account not online"); - return; - } - String getUrl = "https://alexa." + connection.getAmazonSite() + "/" - + uri.substring(PROXY_URI_PART.length()); - - String postData = null; - if ("POST".equals(verb) || "PUT".equals(verb)) { - postData = req.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - } - - this.handleProxyRequest(connection, resp, verb, getUrl, null, postData, true, connection.getAmazonSite()); - return; - } - - // handle post of login page - connection = this.connectionToInitialize; - if (connection == null) { - returnError(resp, "Connection not in initialize mode."); - return; - } - - resp.addHeader("content-type", "text/html;charset=UTF-8"); - - Map map = req.getParameterMap(); - StringBuilder postDataBuilder = new StringBuilder(); - for (String name : map.keySet()) { - if (postDataBuilder.length() > 0) { - postDataBuilder.append('&'); - } - - postDataBuilder.append(name); - postDataBuilder.append('='); - String value = ""; - if ("failedSignInCount".equals(name)) { - value = "ape:AA=="; - } else { - String[] strings = map.get(name); - if (strings != null && strings.length > 0 && strings[0] != null) { - value = strings[0]; - } - } - postDataBuilder.append(URLEncoder.encode(value, StandardCharsets.UTF_8)); - } - - uri = req.getRequestURI(); - if (uri == null || !uri.startsWith(servletUrl)) { - returnError(resp, "Invalid request uri '" + uri + "'"); - return; - } - String relativeUrl = uri.substring(servletUrl.length()).replace(FORWARD_URI_PART, "/"); - - String site = connection.getAmazonSite(); - if (relativeUrl.startsWith("/ap/signin")) { - site = "amazon.com"; - } - String postUrl = "https://www." + site + relativeUrl; - queryString = req.getQueryString(); - if (queryString != null && queryString.length() > 0) { - postUrl += "?" + queryString; - } - String referer = "https://www." + site; - String postData = postDataBuilder.toString(); - handleProxyRequest(connection, resp, "POST", postUrl, referer, postData, false, site); - } - - @Override - protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) throws IOException { - if (req == null) { - return; - } - if (resp == null) { - return; - } - String requestUri = req.getRequestURI(); - if (requestUri == null) { - return; - } - String baseUrl = requestUri.substring(servletUrl.length()); - String uri = baseUrl; - String queryString = req.getQueryString(); - if (queryString != null && queryString.length() > 0) { - uri += "?" + queryString; - } - logger.debug("doGet {}", uri); - try { - Connection connection = this.connectionToInitialize; - if (uri.startsWith(FORWARD_URI_PART) && connection != null) { - String getUrl = "https://www." + connection.getAmazonSite() + "/" - + uri.substring(FORWARD_URI_PART.length()); - - this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite()); - return; - } - - connection = this.account.findConnection(); - if (uri.startsWith(PROXY_URI_PART)) { - // handle proxy request - - if (connection == null) { - returnError(resp, "Account not online"); - return; - } - String getUrl = "https://alexa." + connection.getAmazonSite() + "/" - + uri.substring(PROXY_URI_PART.length()); - - this.handleProxyRequest(connection, resp, "GET", getUrl, null, null, false, connection.getAmazonSite()); - return; - } - - if (connection != null && connection.verifyLogin()) { - // handle commands - if ("/logout".equals(baseUrl) || "/logout/".equals(baseUrl)) { - this.connectionToInitialize = reCreateConnection(); - this.account.setConnection(null); - resp.sendRedirect(this.servletUrl); - return; - } - // handle commands - if ("/newdevice".equals(baseUrl) || "/newdevice/".equals(baseUrl)) { - this.connectionToInitialize = new Connection(null, this.gson); - this.account.setConnection(null); - resp.sendRedirect(this.servletUrl); - return; - } - - if ("/devices".equals(baseUrl) || "/devices/".equals(baseUrl)) { - handleDevices(resp, connection); - return; - } - if ("/changeDomain".equals(baseUrl) || "/changeDomain/".equals(baseUrl)) { - handleChangeDomain(resp, connection); - return; - } - if ("/ids".equals(baseUrl) || "/ids/".equals(baseUrl)) { - String serialNumber = getQueryMap(queryString).get("serialNumber"); - Device device = account.findDeviceJson(serialNumber); - if (device != null) { - Thing thing = account.findThingBySerialNumber(device.serialNumber); - handleIds(resp, connection, device, thing); - return; - } - } - // return hint that everything is ok - handleDefaultPageResult(resp, "The Account is logged in.", connection); - return; - } - connection = this.connectionToInitialize; - if (connection == null) { - connection = this.reCreateConnection(); - this.connectionToInitialize = connection; - } - - if (!"/".equals(uri)) { - String newUri = req.getServletPath() + "/"; - resp.sendRedirect(newUri); - return; - } - - String html = connection.getLoginPage(); - returnHtml(connection, resp, html, "amazon.com"); - } catch (URISyntaxException | ConnectionException e) { - logger.warn("get failed with uri syntax error", e); - } - } - - public Map getQueryMap(@Nullable String query) { - Map map = new HashMap<>(); - if (query != null) { - String[] params = query.split("&"); - for (String param : params) { - String[] elements = param.split("="); - if (elements.length == 2) { - String name = elements[0]; - String value = URLDecoder.decode(elements[1], StandardCharsets.UTF_8); - map.put(name, value); - } - } - } - return map; - } - - private void handleChangeDomain(HttpServletResponse resp, Connection connection) { - StringBuilder html = createPageStart("Change Domain"); - html.append("
\nDomain:\n\n
\n\n
"); - - createPageEndAndSent(resp, html); - } - - private void handleDefaultPageResult(HttpServletResponse resp, String message, Connection connection) - throws IOException { - StringBuilder html = createPageStart(""); - html.append(HtmlEscape.escapeHtml4(message)); - // logout link - html.append(" "); - html.append(HtmlEscape.escapeHtml4("Logout")); - html.append(""); - // newdevice link - html.append(" | "); - html.append(HtmlEscape.escapeHtml4("Logout and create new device id")); - html.append(""); - // customer id - html.append("
Customer Id: "); - html.append(HtmlEscape.escapeHtml4(connection.getCustomerId())); - // customer name - html.append("
Customer Name: "); - html.append(HtmlEscape.escapeHtml4(connection.getCustomerName())); - // device name - html.append("
App name: "); - html.append(HtmlEscape.escapeHtml4(connection.getDeviceName())); - // connection - html.append("
Connected to: "); - html.append(HtmlEscape.escapeHtml4(connection.getAlexaServer())); - // domain - html.append(" Change"); - - // Main UI link - html.append("
"); - html.append(HtmlEscape.escapeHtml4("Check Thing in Main UI")); - html.append("

"); - - // device list - html.append( - ""); - for (Device device : this.account.getLastKnownDevices()) { - - html.append(""); - html.append(""); - } - html.append("
DeviceSerial NumberStateThingFamilyTypeCustomer Id
"); - html.append(HtmlEscape.escapeHtml4(nullReplacement(device.accountName))); - html.append(""); - html.append(HtmlEscape.escapeHtml4(nullReplacement(device.serialNumber))); - html.append(""); - html.append(HtmlEscape.escapeHtml4(device.online ? "Online" : "Offline")); - html.append(""); - Thing accountHandler = account.findThingBySerialNumber(device.serialNumber); - if (accountHandler != null) { - html.append("") - .append(HtmlEscape.escapeHtml4(accountHandler.getLabel())).append(""); - } else { - html.append("") - .append(HtmlEscape.escapeHtml4("Not defined")).append(""); - } - html.append(""); - html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceFamily))); - html.append(""); - String deviceTypeId = nullReplacement(device.deviceType); - html.append(HtmlEscape.escapeHtml4(DEVICE_TYPES.getOrDefault(deviceTypeId, deviceTypeId))); - html.append(""); - html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceOwnerCustomerId))); - html.append("
"); - createPageEndAndSent(resp, html); - } - - private void handleDevices(HttpServletResponse resp, Connection connection) throws ConnectionException { - returnHtml(connection, resp, "" + HtmlEscape.escapeHtml4(connection.getDeviceListJson()) + ""); - } - - private String nullReplacement(@Nullable String text) { - if (text == null) { - return ""; - } - return text; - } - - StringBuilder createPageStart(String title) { - StringBuilder html = new StringBuilder(); - html.append("" - + HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel())); - if (!title.isEmpty()) { - html.append(" - "); - html.append(HtmlEscape.escapeHtml4(title)); - } - html.append(""); - html.append("

" + HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel())); - if (!title.isEmpty()) { - html.append(" - "); - html.append(HtmlEscape.escapeHtml4(title)); - } - html.append("

"); - return html; - } - - private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) { - // account overview link - html.append("
"); - html.append(HtmlEscape.escapeHtml4("Account overview")); - html.append("
"); - - html.append(""); - resp.addHeader("content-type", "text/html;charset=UTF-8"); - try { - resp.getWriter().write(html.toString()); - } catch (IOException e) { - logger.warn("return html failed with IO error", e); - } - } - - private void handleIds(HttpServletResponse resp, Connection connection, Device device, @Nullable Thing thing) - throws IOException, URISyntaxException { - 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(""); - device.getCapabilities().forEach( - capability -> html.append("")); - html.append("
Name
").append(HtmlEscape.escapeHtml4(capability)).append("
"); - } - - private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) { - html.append("

").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_MUSIC_PROVIDER_ID)).append("

"); - html.append(""); - List musicProviders = connection.getMusicProviders(); - for (JsonMusicProvider musicProvider : musicProviders) { - List properties = musicProvider.supportedProperties; - String providerId = musicProvider.id; - String displayName = musicProvider.displayName; - if (properties != null && properties.contains("Alexa.Music.PlaySearchPhrase") && providerId != null - && !providerId.isEmpty() && "AVAILABLE".equals(musicProvider.availability) && displayName != null - && !displayName.isEmpty()) { - html.append(""); - } - } - html.append("
NameValue
"); - html.append(HtmlEscape.escapeHtml4(displayName)); - html.append(""); - html.append(HtmlEscape.escapeHtml4(providerId)); - html.append("
"); - } - - private void renderPlayAlarmSoundChannel(Connection connection, Device device, StringBuilder html) { - html.append("

").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_PLAY_ALARM_SOUND)).append("

"); - List notificationSounds = List.of(); - String errorMessage = "No notifications sounds found"; - try { - notificationSounds = connection.getNotificationSounds(device); - } catch (ConnectionException | JsonSyntaxException e) { - errorMessage = e.getLocalizedMessage(); - } - if (!notificationSounds.isEmpty()) { - html.append(""); - for (JsonNotificationSound notificationSound : notificationSounds) { - if (notificationSound.folder == null && notificationSound.providerId != null - && notificationSound.id != null && notificationSound.displayName != null) { - String providerSoundId = notificationSound.providerId + ":" + notificationSound.id; - - html.append(""); - } - } - html.append("
NameValue
"); - html.append(HtmlEscape.escapeHtml4(notificationSound.displayName)); - html.append(""); - html.append(HtmlEscape.escapeHtml4(providerSoundId)); - html.append("
"); - } else { - html.append(HtmlEscape.escapeHtml4(errorMessage)); - } - } - - private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device device, StringBuilder html) { - html.append("

").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID)) - .append("

"); - - JsonPlaylists playLists = null; - String errorMessage = "No playlists found"; - try { - playLists = connection.getPlaylists(device); - } catch (ConnectionException | JsonSyntaxException e) { - errorMessage = e.getLocalizedMessage(); - } - - if (playLists != null) { - Map playlistMap = playLists.playlists; - if (playlistMap != null && !playlistMap.isEmpty()) { - html.append(""); - - for (PlayList[] innerLists : playlistMap.values()) { - { - if (innerLists != null && innerLists.length > 0) { - PlayList playList = innerLists[0]; - if (playList.playlistId != null && playList.title != null) { - html.append(""); - } - } - } - } - html.append("
NameValue
"); - html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.title))); - html.append(""); - html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.playlistId))); - html.append("
"); - } else { - html.append(HtmlEscape.escapeHtml4(errorMessage)); - } - } - } - - private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) { - html.append("

").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_BLUETOOTH_MAC)).append("

"); - JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates(); - BluetoothState[] innerStates = bluetoothStates.bluetoothStates; - if (innerStates == null) { - return; - } - for (BluetoothState state : innerStates) { - if (state == null) { - continue; - } - String stateDeviceSerialNumber = state.deviceSerialNumber; - if ((stateDeviceSerialNumber == null && device.serialNumber == null) - || (stateDeviceSerialNumber != null && stateDeviceSerialNumber.equals(device.serialNumber))) { - List pairedDeviceList = state.getPairedDeviceList(); - if (!pairedDeviceList.isEmpty()) { - html.append(""); - for (PairedDevice pairedDevice : pairedDeviceList) { - html.append(""); - } - html.append("
NameValue
"); - html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.friendlyName))); - html.append(""); - html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.address))); - html.append("
"); - } else { - html.append(HtmlEscape.escapeHtml4("No bluetooth devices paired")); - } - } - } - } - - void handleProxyRequest(Connection connection, HttpServletResponse resp, String verb, String url, - @Nullable String referer, @Nullable String postData, boolean json, String site) throws IOException { - HttpsURLConnection urlConnection; - try { - Map headers = new HashMap<>(); - if (referer != null) { - headers.put("Referer", referer); - } - - urlConnection = connection.makeRequest(verb, url, postData, json, false, headers, 0); - if (urlConnection.getResponseCode() == 302) { - { - String location = urlConnection.getHeaderField("location"); - if (location.contains("/ap/maplanding")) { - try { - connection.registerConnectionAsApp(location); - account.setConnection(connection); - handleDefaultPageResult(resp, "Login succeeded", connection); - this.connectionToInitialize = null; - return; - } catch (URISyntaxException | ConnectionException e) { - returnError(resp, - "Login to '" + connection.getAmazonSite() + "' failed: " + e.getLocalizedMessage()); - this.connectionToInitialize = null; - return; - } - } - - String startString = "https://www." + connection.getAmazonSite() + "/"; - String newLocation = null; - if (location.startsWith(startString) && connection.getIsLoggedIn()) { - newLocation = servletUrl + PROXY_URI_PART + location.substring(startString.length()); - } else if (location.startsWith(startString)) { - newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length()); - } else { - startString = "/"; - if (location.startsWith(startString)) { - newLocation = servletUrl + FORWARD_URI_PART + location.substring(startString.length()); - } - } - if (newLocation != null) { - logger.debug("Redirect mapped from {} to {}", location, newLocation); - - resp.sendRedirect(newLocation); - return; - } - returnError(resp, "Invalid redirect to '" + location + "'"); - return; - } - } - String response = connection.convertStream(urlConnection); - returnHtml(connection, resp, response, site); - } catch (ConnectionException | InterruptedException e) { - returnError(resp, e.getLocalizedMessage()); - } - } - - private void returnHtml(Connection connection, HttpServletResponse resp, String html) { - returnHtml(connection, resp, html, connection.getAmazonSite()); - } - - private void returnHtml(Connection connection, HttpServletResponse resp, String html, String amazonSite) { - String resultHtml = html.replace("action=\"/", "action=\"" + servletUrl + "/") - .replace("action=\"/", "action=\"" + servletUrl + "/") - .replace("https://www." + amazonSite + "/", servletUrl + "/") - .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/") - .replace("https://www." + amazonSite + "/", servletUrl + "/") - .replace("https://www." + amazonSite + ":443" + "/", servletUrl + "/") - .replace("http://www." + amazonSite + "/", servletUrl + "/") - .replace("http://www." + amazonSite + "/", servletUrl + "/"); - - resp.addHeader("content-type", "text/html;charset=UTF-8"); - try { - resp.getWriter().write(resultHtml); - } catch (IOException e) { - logger.warn("return html failed with IO error", e); - } - } - - void returnError(HttpServletResponse resp, @Nullable String errorMessage) { - try { - String message = errorMessage != null ? errorMessage : "null"; - resp.getWriter().write("" + HtmlEscape.escapeHtml4(message) + "
Try again"); - } catch (IOException e) { - logger.info("Returning error message failed", e); - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java index 3bab791014..4385e31e9f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java @@ -13,11 +13,20 @@ */ package org.smarthomej.binding.amazonechocontrol.internal; +import java.io.InputStreamReader; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.library.types.HSBType; import org.openhab.core.thing.ThingTypeUID; +import org.smarthomej.binding.amazonechocontrol.internal.smarthome.AlexaColor; +import org.smarthomej.commons.util.ResourceUtil; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; /** * The {@link AmazonEchoControlBindingConstants} class defines common constants, which are @@ -36,10 +45,8 @@ public class AmazonEchoControlBindingConstants { public static final ThingTypeUID THING_TYPE_ECHO_SPOT = new ThingTypeUID(BINDING_ID, "echospot"); public static final ThingTypeUID THING_TYPE_ECHO_SHOW = new ThingTypeUID(BINDING_ID, "echoshow"); public static final ThingTypeUID THING_TYPE_ECHO_WHA = new ThingTypeUID(BINDING_ID, "wha"); - public static final ThingTypeUID THING_TYPE_FLASH_BRIEFING_PROFILE = new ThingTypeUID(BINDING_ID, "flashbriefingprofile"); - public static final ThingTypeUID THING_TYPE_SMART_HOME_DEVICE = new ThingTypeUID(BINDING_ID, "smartHomeDevice"); public static final ThingTypeUID THING_TYPE_SMART_HOME_DEVICE_GROUP = new ThingTypeUID(BINDING_ID, "smartHomeDeviceGroup"); @@ -51,14 +58,15 @@ public class AmazonEchoControlBindingConstants { THING_TYPE_SMART_HOME_DEVICE_GROUP); // List of all Channel ids + public static final String CHANNEL_ANNOUNCEMENT = "announcement"; + public static final String CHANNEL_SEND_MESSAGE = "sendMessage"; + public static final String CHANNEL_REFRESH_ACTIVITY = "refreshActivity"; public static final String CHANNEL_PLAYER = "player"; public static final String CHANNEL_VOLUME = "volume"; public static final String CHANNEL_EQUALIZER_TREBLE = "equalizerTreble"; public static final String CHANNEL_EQUALIZER_MIDRANGE = "equalizerMidrange"; public static final String CHANNEL_EQUALIZER_BASS = "equalizerBass"; - public static final String CHANNEL_ERROR = "error"; public static final String CHANNEL_SHUFFLE = "shuffle"; - public static final String CHANNEL_LOOP = "loop"; public static final String CHANNEL_IMAGE_URL = "imageUrl"; public static final String CHANNEL_TITLE = "title"; public static final String CHANNEL_SUBTITLE1 = "subtitle1"; @@ -67,11 +75,6 @@ public class AmazonEchoControlBindingConstants { public static final String CHANNEL_BLUETOOTH_MAC = "bluetoothMAC"; public static final String CHANNEL_BLUETOOTH = "bluetooth"; public static final String CHANNEL_BLUETOOTH_DEVICE_NAME = "bluetoothDeviceName"; - public static final String CHANNEL_RADIO_STATION_ID = "radioStationId"; - public static final String CHANNEL_RADIO = "radio"; - public static final String CHANNEL_AMAZON_MUSIC_TRACK_ID = "amazonMusicTrackId"; - public static final String CHANNEL_AMAZON_MUSIC = "amazonMusic"; - public static final String CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID = "amazonMusicPlayListId"; public static final String CHANNEL_TEXT_TO_SPEECH = "textToSpeech"; public static final String CHANNEL_TEXT_TO_SPEECH_VOLUME = "textToSpeechVolume"; public static final String CHANNEL_TEXT_COMMAND = "textCommand"; @@ -93,7 +96,6 @@ public class AmazonEchoControlBindingConstants { public static final String CHANNEL_NEXT_ALARM = "nextAlarm"; public static final String CHANNEL_NEXT_MUSIC_ALARM = "nextMusicAlarm"; public static final String CHANNEL_NEXT_TIMER = "nextTimer"; - public static final String CHANNEL_SAVE = "save"; public static final String CHANNEL_ACTIVE = "active"; public static final String CHANNEL_PLAY_ON_DEVICE = "playOnDevice"; @@ -110,84 +112,18 @@ public class AmazonEchoControlBindingConstants { // Other public static final String FLASH_BRIEFING_COMMAND_PREFIX = "FlashBriefing."; - // DeviceTypeIds to human readable description - // originally found here: https://github.com/Apollon77/ioBroker.alexa2/blob/master/main.js - public static final Map DEVICE_TYPES = Map. ofEntries( // - Map.entry("A10A33FOX2NUBK", "Echo Spot"), // - Map.entry("A10L5JEZTKKCZ8", "Vobot-Clock"), // - Map.entry("A12GXV8XMS007S", "FireTV"), // - Map.entry("A15ERDAKK5HQQG", "Sonos"), // - Map.entry("A17LGWINFBUTZZ", "Anker Roav Viva Alexa"), // - Map.entry("A18O6U1UQFJ0XK", "Echo Plus 2nd Gen"), // - Map.entry("A1C66CX2XD756O", "Fire HD 8"), // - Map.entry("A1DL2DVDQVK3Q", "Apps"), // - Map.entry("A1ETW4IXK2PYBP", "Echo Auto"), // - Map.entry("A1H0CMF1XM0ZP4", "Echo Dot/Bose"), // - Map.entry("A1J16TEDOYCZTN", "Fire Tab"), // - Map.entry("A1JJ0KFC4ZPNJ3", "Echo Input"), // - Map.entry("A1NL4BVLQ4L3N3", "Echo Show"), // - Map.entry("A1P31Q3MOWSHOD", "Anker Zalo Halo Speaker"), // - Map.entry("A1Q7QCGNMXAKYW", "Fire Tab 7"), // - Map.entry("A1QKZ9D0IJY332", "Samsung QLED"), // - Map.entry("A1RABVCI4QCIKC", "Echo Dot 3rd Gen"), // - Map.entry("A1RTAM01W29CUP", "Windows App"), // - Map.entry("A1X7HJX9QL16M5", "Bespoken.io"), // - Map.entry("A1Z88NGR2BK6A2", "Echo Show 8"), // - Map.entry("A1ZB65LA390I4K", "Fire HD 10"), // - Map.entry("A21Z3CGI8UIP0F", "Apps"), // - Map.entry("A265XOI9586NML", "FireTV Stick v3"), // - Map.entry("A2825NDLA7WDZV", "Apps"), // - Map.entry("A2E0SNTXJVT7WK", "FireTV V1"), // - Map.entry("A2GFL5ZMWNE0PX", "FireTV"), // - Map.entry("A2H4LV5GIZ1JFT", "Echo 4 Clock"), // - Map.entry("A2IVLV5VM2W81", "Apps"), // - Map.entry("A2J0R2SD7G9LPA", "Tablet"), // - Map.entry("A2JKHJ0PX4J3L3", "FireTV Cube"), // - Map.entry("A2L8KG0CT86ADW", "RaspPi"), // - Map.entry("A2LWARUGJLBYEW", "FireTV Stick V2"), // - Map.entry("A2M35JJZWCQOMZ", "Echo Plus"), // - Map.entry("A2M4YX06LWP8WI", "Fire Tab"), // - Map.entry("A2OSP3UA4VC85F", "Sonos"), // - Map.entry("A2T0P32DY3F7VB", "echosim.io"), // - Map.entry("A2TF17PFR55MTB", "Apps"), // - Map.entry("A2U21SRK4QGSE1", "Echo Dot 4th Gen"), // - Map.entry("A2Z8O30CD35N8F", "Sonos Arc"), // - Map.entry("A303PJF6ISQ7IC", "Echo Auto"), // - Map.entry("A30YDR2MK8HMRV", "Echo Dot 3rd Gen Clock"), // - Map.entry("A31DTMEEVDDOIV", "FireTV Stick Lite 2020"), // - Map.entry("A32DOYMUN6DTXA", "Echo Dot 3rd Gen"), // - Map.entry("A378ND93PD0NC4", "VR Radio"), // - Map.entry("A37SHHQ3NUL7B5", "Bose Homespeaker"), // - Map.entry("A38BPK7OW001EX", "Raspberry Alexa"), // - Map.entry("A38EHHIB10L47V", "Echo Dot"), // - Map.entry("A39Y3UG1XLEJLZ", "Fitbit Sense"), // - Map.entry("A3C9PE6TNYLTCH", "Multiroom"), // - Map.entry("A3FX4UWTP28V1P", "Echo 3"), // - Map.entry("A3GZUE7F9MEB4U", "FireTV Cube"), // - Map.entry("A3H674413M2EKB", "echosim.io"), // - Map.entry("A3HF4YRA2L7XGC", "FireTV Cube"), // - Map.entry("A3NPD82ABCPIDP", "Sonos Beam"), // - Map.entry("A3R8XIAIU4HJAX", "Echo Show"), // - Map.entry("A3R9S4ZZECZ6YL", "Fire Tab HD 10"), // - Map.entry("A3RBAYBE7VM004", "Echo Studio"), // - Map.entry("A3RMGO6LYLH7YN", "Echo 4 Bridge"), // - Map.entry("A3S5BH2HU6VAYF", "Echo Dot 2nd Gen"), // - Map.entry("A3SSG6GR8UU7SN", "Echo Sub"), // - Map.entry("A3TCJ8RTT3NVI7", "Listens for Alexa"), // - Map.entry("A3V3VA38K169FO", "Fire Tab"), // - Map.entry("A3VRME03NAXFUB", "Echo Flex"), // - Map.entry("A4ZP7ZC4PI6TO", "Echo Show 5th Gen"), // - Map.entry("A7WXQPH584YP", "Echo 2nd Gen"), // - Map.entry("A8DM4FYR6D3HT", "LG WebOS TV"), // - Map.entry("AB72C64C86AW2", "Echo"), // - Map.entry("ADVBD696BHNV5", "FireTV Stick V1"), // - Map.entry("AILBSA2LNTOYL", "reverb App"), // - Map.entry("AINRG27IL8AS0", "Megablast Speaker"), // - Map.entry("AKOAGQTKAS9YB", "Echo Connect"), // - Map.entry("AKPGW064GI9HE", "FireTV Stick 4K"), // - Map.entry("AP1F6KUH00XPV", "Stereo/Subwoofer Pair"), // - Map.entry("AVD3HM0HOJAAL", "Sonos One 2nd Gen"), // - Map.entry("AVE5HX13UR5NO", "Logitech Zero Touch"), // - Map.entry("AVU7CPPF2ZRAS", "Fire HD 8"), // - Map.entry("AWZZ5CVHX2CD", "Echo Show 2nd Gen")); + public static final String API_VERSION = "2.2.556530.0"; + public static final String DI_OS_VERSION = "16.6"; + public static final String DI_SDK_VERSION = "6.12.4"; + + public static final Map DEVICE_TYPES = ResourceUtil + .readProperties(AmazonEchoControlBindingConstants.class, "device_type.properties"); + + public static final JsonObject CAPABILITY_REGISTRATION = Objects.requireNonNull( + ResourceUtil.getResourceStream(AmazonEchoControlBindingConstants.class, "registration_capabilities.json") + .map(inputStream -> new Gson().fromJson(new InputStreamReader(inputStream), JsonObject.class)) + .orElseThrow(() -> new IllegalStateException("resource not found"))); + public static final List ALEXA_COLORS = ResourceUtil + .readProperties(AlexaColor.class, "color.properties").entrySet().stream() + .map(e -> new AlexaColor(e.getKey(), new HSBType(e.getValue()))).toList(); } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlCommandDescriptionProvider.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlCommandDescriptionProvider.java new file mode 100644 index 0000000000..fae8d3b3f0 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlCommandDescriptionProvider.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; + +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider; +import org.openhab.core.thing.type.DynamicCommandDescriptionProvider; +import org.openhab.core.types.CommandOption; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; +import org.smarthomej.binding.amazonechocontrol.internal.handler.EchoHandler; +import org.smarthomej.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler; + +/** + * The {@link AmazonEchoControlCommandDescriptionProvider} implements dynamic command description provider for the + * amazonechocontrol binding + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(service = { DynamicCommandDescriptionProvider.class, AmazonEchoControlCommandDescriptionProvider.class }) +public class AmazonEchoControlCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider { + private final Logger logger = LoggerFactory.getLogger(AmazonEchoControlCommandDescriptionProvider.class); + + public void setFlashBriefingTargets(Collection flashBriefingProfileHandlers, + Collection targets) { + List options = new ArrayList<>(); + options.add(new CommandOption("", "")); + for (DeviceTO device : targets) { + final String value = device.serialNumber; + if (value != null && device.capabilities.contains("FLASH_BRIEFING")) { + options.add(new CommandOption(value, device.accountName)); + } + } + + for (FlashBriefingProfileHandler flashBriefingProfileHandler : flashBriefingProfileHandlers) { + ChannelUID channelUID = new ChannelUID(flashBriefingProfileHandler.getThing().getUID(), + CHANNEL_PLAY_ON_DEVICE); + if (options.isEmpty()) { + channelOptionsMap.remove(channelUID); + } else { + channelOptionsMap.put(channelUID, options); + } + } + } + + public void setEchoHandlerStartCommands(Collection echoHandlers, + Collection flashBriefingProfileHandlers) { + List options = new ArrayList<>(); + options.add(new CommandOption("Weather", "Weather")); + options.add(new CommandOption("Traffic", "Traffic")); + options.add(new CommandOption("GoodMorning", "Good morning")); + options.add(new CommandOption("SingASong", "Song")); + options.add(new CommandOption("TellStory", "Story")); + options.add(new CommandOption("FlashBriefing", "Flash briefing")); + + for (FlashBriefingProfileHandler flashBriefing : flashBriefingProfileHandlers) { + String value = FLASH_BRIEFING_COMMAND_PREFIX + flashBriefing.getThing().getUID().getId(); + String displayName = flashBriefing.getThing().getLabel(); + options.add(new CommandOption(value, displayName)); + } + + for (EchoHandler echoHandler : echoHandlers) { + ChannelUID channelUID = new ChannelUID(echoHandler.getThing().getUID(), CHANNEL_START_COMMAND); + if (options.isEmpty()) { + channelOptionsMap.remove(channelUID); + } else { + channelOptionsMap.put(channelUID, options); + } + } + } + + public void setEchoHandlerAlarmSounds(EchoHandler echoHandler, List alarmSounds) { + List options = new ArrayList<>(); + for (NotificationSoundTO notificationSound : alarmSounds) { + if (notificationSound.folder == null && notificationSound.providerId != null && notificationSound.id != null + && notificationSound.displayName != null) { + String providerSoundId = notificationSound.providerId + ":" + notificationSound.id; + options.add(new CommandOption(providerSoundId, notificationSound.displayName)); + } + } + + ChannelUID channelUID = new ChannelUID(echoHandler.getThing().getUID(), CHANNEL_PLAY_ALARM_SOUND); + if (options.isEmpty()) { + channelOptionsMap.remove(channelUID); + } else { + channelOptionsMap.put(channelUID, options); + } + } + + public void removeCommandDescriptionForThing(ThingUID thingUID) { + logger.trace("removing state description for thing {}", thingUID); + channelOptionsMap.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID)); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java index f3056e197f..cb7b3b1341 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java @@ -21,7 +21,8 @@ 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.http2.client.HTTP2Client; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.storage.Storage; import org.openhab.core.storage.StorageService; import org.openhab.core.thing.Bridge; @@ -30,20 +31,21 @@ import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.http.HttpService; import org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler; import org.smarthomej.binding.amazonechocontrol.internal.handler.EchoHandler; import org.smarthomej.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; +import org.smarthomej.binding.amazonechocontrol.internal.util.NonNullListTypeAdapterFactory; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNullTypeAdapterFactory; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; import org.smarthomej.commons.SimpleDynamicStateDescriptionProvider; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; /** * The {@link AmazonEchoControlHandlerFactory} is responsible for creating things and thing @@ -56,33 +58,43 @@ @NonNullByDefault public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory { private final Set accountHandlers = new HashSet<>(); - private final HttpService httpService; private final StorageService storageService; - private final BindingServlet bindingServlet; private final Gson gson; private final HttpClient httpClient; + private final HTTP2Client http2Client; + private final SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider; private final SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider; + private final AmazonEchoControlCommandDescriptionProvider amazonEchoControlCommandDescriptionProvider; @Activate - public AmazonEchoControlHandlerFactory(@Reference HttpService httpService, @Reference StorageService storageService, + public AmazonEchoControlHandlerFactory(@Reference StorageService storageService, @Reference SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider, - @Reference SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider) throws Exception { + @Reference SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider, + @Reference HttpClientFactory httpClientFactory, + @Reference AmazonEchoControlCommandDescriptionProvider amazonEchoControlCommandDescriptionProvider) + throws Exception { this.storageService = storageService; - this.httpService = httpService; - this.gson = new Gson(); + this.gson = new GsonBuilder().registerTypeAdapterFactory(new NonNullListTypeAdapterFactory()) + .registerTypeAdapterFactory(new SerializeNullTypeAdapterFactory()).create(); this.dynamicCommandDescriptionProvider = dynamicCommandDescriptionProvider; this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider; - this.httpClient = new HttpClient(new SslContextFactory.Client()); - this.bindingServlet = new BindingServlet(httpService); + this.amazonEchoControlCommandDescriptionProvider = amazonEchoControlCommandDescriptionProvider; + + this.httpClient = httpClientFactory.createHttpClient("smarthomej-aec"); + this.http2Client = httpClientFactory.createHttp2Client("smarthomej-aec", httpClient.getSslContextFactory()); + http2Client.setConnectTimeout(10000); + http2Client.setIdleTimeout(-1); httpClient.start(); + http2Client.start(); } @Deactivate @SuppressWarnings("unused") public void deactivate() throws Exception { + http2Client.stop(); httpClient.stop(); } @@ -92,29 +104,21 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { || SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID); } - @Override - protected void deactivate(ComponentContext componentContext) { - bindingServlet.dispose(); - super.deactivate(componentContext); - } - @Override protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { Storage storage = storageService.getStorage(thing.getUID().toString(), String.class.getClassLoader()); - AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage, gson, httpClient); + AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, storage, gson, httpClient, http2Client, + amazonEchoControlCommandDescriptionProvider); accountHandlers.add(bridgeHandler); - bindingServlet.addAccountThing(thing); return bridgeHandler; } else if (thingTypeUID.equals(THING_TYPE_FLASH_BRIEFING_PROFILE)) { - Storage storage = storageService.getStorage(thing.getUID().toString(), - String.class.getClassLoader()); - return new FlashBriefingProfileHandler(thing, storage, dynamicCommandDescriptionProvider); + Storage storage = storageService.getStorage(thing.getUID().toString()); + return new FlashBriefingProfileHandler(thing, storage, gson); } else if (SUPPORTED_ECHO_THING_TYPES_UIDS.contains(thingTypeUID)) { - return new EchoHandler(thing, gson, dynamicCommandDescriptionProvider, dynamicStateDescriptionProvider); + return new EchoHandler(thing, gson, dynamicStateDescriptionProvider); } else if (SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID)) { return new SmartHomeDeviceHandler(thing, gson, dynamicCommandDescriptionProvider, dynamicStateDescriptionProvider); @@ -124,10 +128,9 @@ protected void deactivate(ComponentContext componentContext) { @Override protected synchronized void removeHandler(ThingHandler thingHandler) { + amazonEchoControlCommandDescriptionProvider.removeCommandDescriptionForThing(thingHandler.getThing().getUID()); if (thingHandler instanceof AccountHandler) { accountHandlers.remove(thingHandler); - BindingServlet bindingServlet = this.bindingServlet; - bindingServlet.removeAccountThing(thingHandler.getThing()); } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlServlet.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlServlet.java new file mode 100644 index 0000000000..4605e0e733 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/AmazonEchoControlServlet.java @@ -0,0 +1,517 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; + +import static org.eclipse.jetty.util.StringUtil.isNotBlank; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlServlet.SERVLET_PATH; +import static org.smarthomej.binding.amazonechocontrol.internal.util.Util.findIn; +import static org.unbescape.html.HtmlEscape.escapeHtml4; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.servlet.Servlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.util.introspection.UberspectImpl; +import org.apache.velocity.util.introspection.UberspectPublicFields; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.MimeTypes; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletName; +import org.osgi.service.http.whiteboard.propertytypes.HttpWhiteboardServletPattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MusicProviderTO; +import org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler; +import org.smarthomej.binding.amazonechocontrol.internal.util.HttpRequestBuilder; + +/** + * The {@link AmazonEchoControlServlet} allows to log in to Amazon accounts using a proxy and shows information about + * configured accounts and devices + * + * @author Michael Geramb - Initial Contribution + * @author Jan N. Klug - Refactored to whiteboard, merged both servlets, use Velocity templates + */ +@Component(service = Servlet.class, immediate = true) +@HttpWhiteboardServletName(SERVLET_PATH) +@HttpWhiteboardServletPattern({ SERVLET_PATH, SERVLET_PATH + "/*" }) +@NonNullByDefault +public class AmazonEchoControlServlet extends HttpServlet { + public static final String SERVLET_PATH = "/" + BINDING_ID; + private static final long serialVersionUID = -9158865063627039237L; + private static final String FORWARD_URI_PART = "/FORWARD/"; + private static final String PROXY_URI_PART = "/PROXY/"; + + private final Logger logger = LoggerFactory.getLogger(AmazonEchoControlServlet.class); + private final VelocityEngine velocityEngine = new VelocityEngine(); + + private final AmazonEchoControlHandlerFactory handlerFactory; + + @Activate + public AmazonEchoControlServlet(@Reference AmazonEchoControlHandlerFactory handlerFactory) { + this.handlerFactory = handlerFactory; + + velocityEngine.setProperty("introspector.uberspect.class", + UberspectImpl.class.getName() + ", " + UberspectPublicFields.class.getName()); + velocityEngine.init(); + } + + private @Nullable AccountHandler getAccountHandler(String accountUid) { + ThingUID thingUID = new ThingUID(THING_TYPE_ACCOUNT, URLDecoder.decode(accountUid, StandardCharsets.UTF_8)); + return handlerFactory.getAccountHandlers().stream().filter(h -> thingUID.equals(h.getThing().getUID())) + .findAny().orElse(null); + } + + @Override + protected void doPut(@NonNullByDefault({}) HttpServletRequest req, @NonNullByDefault({}) HttpServletResponse resp) + throws IOException { + preProcess(HttpMethod.PUT, req, resp); + } + + @Override + protected void doDelete(@NonNullByDefault({}) HttpServletRequest req, + @NonNullByDefault({}) HttpServletResponse resp) throws IOException { + preProcess(HttpMethod.DELETE, req, resp); + } + + @Override + protected void doPost(@NonNullByDefault({}) HttpServletRequest req, @NonNullByDefault({}) HttpServletResponse resp) + throws IOException { + preProcess(HttpMethod.POST, req, resp); + } + + @Override + protected void doGet(@NonNullByDefault({}) HttpServletRequest req, @NonNullByDefault({}) HttpServletResponse resp) + throws IOException { + preProcess(HttpMethod.GET, req, resp); + } + + private void preProcess(HttpMethod method, HttpServletRequest req, HttpServletResponse resp) throws IOException { + ServletUri servletUri = ServletUri.fromFullUri(req.getRequestURI()); + if (servletUri == null) { + returnError(resp, null, "Could not parse URI for " + method + "/" + req.getRequestURI()); + return; + } + if ("static".equals(servletUri.account())) { + serveStatic(resp, servletUri.request()); + } else if (!servletUri.account().isBlank()) { + switch (method) { + case DELETE, POST, PUT -> doAccountDeletePostPut(method, servletUri, req, resp); + case GET -> doAccountGet(servletUri, req, resp); + default -> returnError(resp, servletUri, "Can't handle " + method + " request for accounts."); + } + } else { + if (HttpMethod.GET.equals(method)) { + doBindingGet(resp); + } else { + returnError(resp, servletUri, "Can't handle " + method + " requests for the binding."); + } + } + } + + private void doBindingGet(HttpServletResponse resp) throws IOException { + VelocityContext ctx = new VelocityContext(); + ctx.put("servletPath", SERVLET_PATH); + ctx.put("accounts", handlerFactory.getAccountHandlers().stream() + .sorted(Comparator.comparing(h -> h.getThing().getUID().toString())).toList()); + + StringWriter stringWriter = evaluateTemplate("WEB-INF/binding.vm", ctx); + + resp.addHeader(HttpHeader.CONTENT_TYPE.asString(), MimeTypes.Type.TEXT_HTML_UTF_8.asString()); + resp.getWriter().write(stringWriter.toString()); + } + + private void doAccountDeletePostPut(HttpMethod method, ServletUri uriParts, HttpServletRequest req, + HttpServletResponse resp) throws IOException { + String uri = uriParts.request(); + String queryString = req.getQueryString(); + if (queryString != null && !queryString.isEmpty()) { + uri += "?" + queryString; + } + + AccountHandler accountHandler = getAccountHandler(uriParts.account()); + if (accountHandler == null) { + returnError(resp, uriParts, "Could not find account handler"); + return; + } + Connection connection = accountHandler.getConnection(); + if (uri.startsWith(PROXY_URI_PART)) { + // handle proxy request + String proxyUrl = connection.getAlexaServer() + "/" + uri.substring(PROXY_URI_PART.length()); + + Object postData = null; + if (HttpMethod.PUT.equals(method) || HttpMethod.POST.equals(method)) { + postData = req.getReader().lines().collect(Collectors.joining()); + } + + this.handleProxyRequest(accountHandler, connection, resp, uriParts, method, proxyUrl, null, postData, + postData != null, connection.getRetailDomain()); + return; + } + + // handle post of login page + if (connection.isLoggedIn()) { + returnError(resp, uriParts, "Connection not in initialize mode."); + return; + } + + resp.addHeader(HttpHeader.CONTENT_TYPE.asString(), MimeTypes.Type.TEXT_HTML_UTF_8.asString()); + + Map map = req.getParameterMap(); + StringBuilder postDataBuilder = new StringBuilder(); + for (String name : map.keySet()) { + if (!postDataBuilder.isEmpty()) { + postDataBuilder.append('&'); + } + + postDataBuilder.append(name); + postDataBuilder.append('='); + String value = ""; + if ("failedSignInCount".equals(name)) { + value = "ape:AA=="; + } else { + String[] strings = map.get(name); + if (strings != null && strings.length > 0) { + value = strings[0]; + } + } + postDataBuilder.append(URLEncoder.encode(value, StandardCharsets.UTF_8)); + } + + String relativeUrl = uriParts.request().replace(FORWARD_URI_PART, "/"); + + String retailDomain = relativeUrl.startsWith("/ap/signin") ? "amazon.com" : connection.getRetailDomain(); + String postUrl = "https://www." + retailDomain + relativeUrl; + queryString = req.getQueryString(); + if (isNotBlank(queryString)) { + postUrl += "?" + queryString; + } + String referer = "https://www." + retailDomain; + String postData = postDataBuilder.toString(); + handleProxyRequest(accountHandler, connection, resp, uriParts, method, postUrl, referer, postData, false, + retailDomain); + } + + private void doAccountGet(ServletUri uriParts, HttpServletRequest req, HttpServletResponse resp) + throws IOException { + String uri = uriParts.request(); + String queryString = req.getQueryString(); + if (isNotBlank(queryString)) { + uri += "?" + queryString; + } + try { + AccountHandler accountHandler = getAccountHandler(uriParts.account()); + if (accountHandler == null) { + returnError(resp, uriParts, "Could not find account handler."); + return; + } + + Connection connection = accountHandler.getConnection(); + if (uri.startsWith(FORWARD_URI_PART)) { + String getUrl = connection.getRetailUrl() + "/" + uri.substring(FORWARD_URI_PART.length()); + + this.handleProxyRequest(accountHandler, connection, resp, uriParts, HttpMethod.GET, getUrl, null, null, + false, connection.getRetailDomain()); + return; + } + + if (uri.startsWith(PROXY_URI_PART)) { + // handle proxy request + String proxyUrl = connection.getAlexaServer() + "/" + uri.substring(PROXY_URI_PART.length()); + + this.handleProxyRequest(accountHandler, connection, resp, uriParts, HttpMethod.GET, proxyUrl, null, + null, false, connection.getRetailDomain()); + return; + } + + if (connection.verifyLogin()) { + // handle commands + if ("/logout".equals(uriParts.request()) || "/logout/".equals(uriParts.request())) { + accountHandler.resetConnection(false); + resp.sendRedirect(uriParts.buildFor("/")); + return; + } + // handle commands + if ("/newdevice".equals(uriParts.request()) || "/newdevice/".equals(uriParts.request())) { + accountHandler.resetConnection(true); + resp.sendRedirect(uriParts.buildFor("/")); + return; + } + if ("/ids".equals(uriParts.request()) || "/ids/".equals(uriParts.request())) { + String serialNumber = getQueryMap(queryString).get("serialNumber"); + DeviceTO device = accountHandler.findDevice(serialNumber); + if (device != null) { + Thing thing = accountHandler.getThingBySerialNumber(device.serialNumber); + if (thing == null) { + returnError(resp, uriParts, "No thing defined for " + serialNumber); + } else { + createDeviceDetailsResponse(resp, uriParts, connection, device, thing); + } + return; + } + } + // return hint that everything is ok + createAccountPage(resp, uriParts, accountHandler, connection); + return; + } + + if (!uriParts.request().isBlank()) { + resp.sendRedirect(SERVLET_PATH + "/" + uriParts.account()); + return; + } + + String html = connection.getLoginPage(); + returnHtml(resp, uriParts, html, "amazon.com"); + } catch (ConnectionException e) { + logger.warn("get failed with uri syntax error", e); + } + } + + private void createAccountPage(HttpServletResponse resp, ServletUri uriParts, AccountHandler accountHandler, + Connection connection) throws IOException { + VelocityContext ctx = new VelocityContext(); + ctx.put("servletPath", SERVLET_PATH); + ctx.put("accountPath", uriParts.buildFor("/")); + ctx.put("account", accountHandler); + ctx.put("connection", connection); + ctx.put("devices", accountHandler.getLastKnownDevices().stream() + .sorted(Comparator.comparing(d -> d.serialNumber)).toList()); + ctx.put("DEVICE_TYPES", DEVICE_TYPES); + + StringWriter stringWriter = evaluateTemplate("WEB-INF/account-detail.vm", ctx); + resp.addHeader(HttpHeader.CONTENT_TYPE.asString(), MimeTypes.Type.TEXT_HTML_UTF_8.asString()); + resp.getWriter().write(stringWriter.toString()); + } + + private void createDeviceDetailsResponse(HttpServletResponse resp, ServletUri uriParts, Connection connection, + DeviceTO device, Thing thing) throws IOException { + Map> channels = new HashMap<>(); + List musicProviders = connection.getMusicProviders().stream().filter(this::isValidMusicProvider) + .map(p -> new ChannelOption(p.id, p.displayName)).sorted(Comparator.comparing(o -> o.value)).toList(); + channels.put(CHANNEL_MUSIC_PROVIDER_ID, musicProviders); + + List alarmSounds = connection.getNotificationSounds(device).stream() + .filter(this::isValidAlarmSound).map(p -> new ChannelOption(p.providerId + ":" + p.id, p.displayName)) + .sorted(Comparator.comparing(o -> o.value)).toList(); + channels.put(CHANNEL_PLAY_ALARM_SOUND, alarmSounds); + + List states = connection.getBluetoothConnectionStates(); + List pairedDevices = findIn(states, k -> k.deviceSerialNumber, device.serialNumber) + .map(state -> state.pairedDeviceList) + .map(list -> list.stream().map(p -> new ChannelOption(p.address, p.friendlyName)) + .sorted(Comparator.comparing(o -> o.value)).toList()) + .orElse(List.of()); + channels.put(CHANNEL_BLUETOOTH_MAC, Objects.requireNonNull(pairedDevices)); + + VelocityContext ctx = new VelocityContext(); + ctx.put("thing", thing); + ctx.put("servletPath", SERVLET_PATH); + ctx.put("accountPath", uriParts.buildFor("/")); + ctx.put("channels", channels); + ctx.put("capabilities", device.capabilities.stream().sorted().toList()); + + StringWriter stringWriter = evaluateTemplate("WEB-INF/device-detail.vm", ctx); + resp.addHeader(HttpHeader.CONTENT_TYPE.asString(), MimeTypes.Type.TEXT_HTML_UTF_8.asString()); + resp.getWriter().write(stringWriter.toString()); + } + + private boolean isValidMusicProvider(MusicProviderTO provider) { + return provider.supportedProperties.contains("Alexa.Music.PlaySearchPhrase") + && "AVAILABLE".equals(provider.availability) && isNotBlank(provider.displayName); + } + + private boolean isValidAlarmSound(NotificationSoundTO sound) { + return sound.folder == null && sound.providerId != null && sound.id != null && sound.displayName != null; + } + + private void handleProxyRequest(AccountHandler accountHandler, Connection connection, HttpServletResponse resp, + ServletUri uriParts, HttpMethod method, String url, @Nullable String referer, @Nullable Object postData, + boolean isJson, String retailDomain) throws IOException { + try { + Map headers = new HashMap<>(); + if (referer != null) { + headers.put(HttpHeader.REFERER.asString(), referer); + } + + HttpRequestBuilder.HttpResponse response = connection.getRequestBuilder().builder(method, url) + .withContent(postData).withJson(isJson).withHeaders(headers).retry(false).redirect(false) + .syncSend(); + if (response.statusCode() == HttpStatus.FOUND_302) { + String location = response.headers().get("location"); + if (location.contains("/ap/maplanding")) { + try { + URI oAuthRedirectUri = new URI(location); + String accessToken = getQueryMap(oAuthRedirectUri.getQuery()).get("openid.oa2.access_token"); + if (accessToken == null) { + returnError(resp, uriParts, + "Login to '" + retailDomain + "' failed: Could not extract accessToken."); + } else if (connection.registerConnectionAsApp(accessToken)) { + accountHandler.setConnection(connection); + resp.sendRedirect(SERVLET_PATH + "/" + uriParts.account()); + // createAccountPage(resp, uriParts, accountHandler, connection); + } else { + returnError(resp, uriParts, + "Login to '" + retailDomain + "' failed: Could not register as app."); + } + return; + } catch (URISyntaxException e) { + returnError(resp, uriParts, + "Login to '" + retailDomain + "' failed: " + e.getLocalizedMessage()); + accountHandler.resetConnection(false); + return; + } + } + + String startString = connection.getRetailUrl() + "/"; + String newLocation = null; + if (location.startsWith(startString) && connection.isLoggedIn()) { + newLocation = uriParts.buildFor(PROXY_URI_PART + location.substring(startString.length())); + } else if (location.startsWith(startString)) { + newLocation = uriParts.buildFor(FORWARD_URI_PART + location.substring(startString.length())); + } else { + startString = "/"; + if (location.startsWith(startString)) { + newLocation = uriParts.buildFor(FORWARD_URI_PART + location.substring(startString.length())); + } + } + if (newLocation != null) { + logger.debug("Redirect mapped from {} to {}", location, newLocation); + resp.sendRedirect(newLocation); + return; + } + returnError(resp, uriParts, "Invalid redirect to '" + location + "'"); + return; + } + returnHtml(resp, uriParts, response.content(), retailDomain); + } catch (ConnectionException e) { + returnError(resp, uriParts, e.getLocalizedMessage()); + } + } + + private void returnHtml(HttpServletResponse resp, ServletUri uriParts, String html, String retailDomain) + throws IOException { + String servletUrl = uriParts.buildFor("/"); + String resultHtml = html.replace("action=\"/", "action=\"" + servletUrl) + .replace("action=\"/", "action=\"" + servletUrl) + .replace("https://www." + retailDomain + "/", servletUrl) + .replace("https://www." + retailDomain + ":443" + "/", servletUrl) + .replace("https://www." + retailDomain + "/", servletUrl) + .replace("https://www." + retailDomain + ":443" + "/", servletUrl) + .replace("http://www." + retailDomain + "/", servletUrl) + .replace("http://www." + retailDomain + "/", servletUrl); + resp.addHeader(HttpHeader.CONTENT_TYPE.asString(), MimeTypes.Type.TEXT_HTML_UTF_8.asString()); + resp.getWriter().write(resultHtml); + } + + void returnError(HttpServletResponse resp, @Nullable ServletUri uriParts, @Nullable String errorMessage) + throws IOException { + String message = errorMessage != null ? errorMessage : "null"; + String tryAgainUri = uriParts == null ? SERVLET_PATH + "/" : uriParts.buildFor("/"); + resp.getWriter() + .write("" + escapeHtml4(message) + "
Try again"); + } + + private Map getQueryMap(@Nullable String query) { + Map map = new HashMap<>(); + if (query != null) { + String[] params = query.split("&"); + for (String param : params) { + String[] elements = param.split("="); + if (elements.length == 2) { + String name = elements[0]; + String value = URLDecoder.decode(elements[1], StandardCharsets.UTF_8); + map.put(name, value); + } + } + } + return map; + } + + private StringWriter evaluateTemplate(String template, VelocityContext ctx) { + StringWriter stringWriter = new StringWriter(); + ClassLoader classLoader = AmazonEchoControlServlet.class.getClassLoader(); + if (classLoader == null) { + return stringWriter; + } + try (InputStream inputStream = classLoader.getResourceAsStream(template)) { + if (inputStream != null) { + Reader reader = new InputStreamReader(inputStream); + velocityEngine.evaluate(ctx, stringWriter, "VTL", reader); + } + } catch (IOException ignored) { + } + return stringWriter; + } + + private void serveStatic(HttpServletResponse resp, String file) throws IOException { + ClassLoader classLoader = AmazonEchoControlServlet.class.getClassLoader(); + if (classLoader == null) { + resp.sendError(500); + return; + } + try (InputStream inputStream = classLoader.getResourceAsStream("WEB-INF" + file)) { + if (inputStream != null) { + String content = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("\n")); + resp.getWriter().write(content); + return; + } + } catch (IOException ignored) { + } + resp.sendError(404); + } + + public static class ChannelOption { + public String value; + public String displayName; + + public ChannelOption(String value, String displayName) { + this.value = value; + this.displayName = displayName; + } + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/BindingServlet.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/BindingServlet.java deleted file mode 100644 index e86deecc4d..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/BindingServlet.java +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; - -import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_NAME; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.Thing; -import org.osgi.service.http.HttpService; -import org.osgi.service.http.NamespaceException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.unbescape.html.HtmlEscape; - -/** - * This servlet provides the base navigation page, with hyperlinks for the defined account things - * - * @author Michael Geramb - Initial Contribution - */ -@NonNullByDefault -public class BindingServlet extends HttpServlet { - private static final long serialVersionUID = -1453738923337413163L; - - private final Logger logger = LoggerFactory.getLogger(BindingServlet.class); - - private final String servletUrl; - private final HttpService httpService; - - private final List accountHandlers = new ArrayList<>(); - - public BindingServlet(HttpService httpService) { - this.httpService = httpService; - String servletUrlWithoutRoot = "amazonechocontrol"; - servletUrl = "/" + servletUrlWithoutRoot; - try { - httpService.registerServlet(servletUrl, this, null, httpService.createDefaultHttpContext()); - } catch (NamespaceException | ServletException e) { - logger.warn("Register servlet fails", e); - } - } - - public void addAccountThing(Thing accountThing) { - synchronized (accountHandlers) { - accountHandlers.add(accountThing); - } - } - - public void removeAccountThing(Thing accountThing) { - synchronized (accountHandlers) { - accountHandlers.remove(accountThing); - } - } - - public void dispose() { - httpService.unregister(servletUrl); - } - - @Override - protected void doGet(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp) - throws ServletException, IOException { - if (req == null) { - return; - } - if (resp == null) { - return; - } - String requestUri = req.getRequestURI(); - if (requestUri == null) { - return; - } - String uri = requestUri.substring(servletUrl.length()); - String queryString = req.getQueryString(); - if (queryString != null && queryString.length() > 0) { - uri += "?" + queryString; - } - logger.debug("doGet {}", uri); - - if (!"/".equals(uri)) { - String newUri = req.getServletPath() + "/"; - resp.sendRedirect(newUri); - return; - } - - StringBuilder html = new StringBuilder(); - html.append("").append(HtmlEscape.escapeHtml4(BINDING_NAME)).append(""); - html.append("

").append(HtmlEscape.escapeHtml4(BINDING_NAME)).append("

"); - - synchronized (accountHandlers) { - if (accountHandlers.isEmpty()) { - html.append("No Account thing created."); - } else { - for (Thing accountHandler : accountHandlers) { - String url = URLEncoder.encode(accountHandler.getUID().getId(), StandardCharsets.UTF_8); - html.append("") - .append(HtmlEscape.escapeHtml4(accountHandler.getLabel())).append("
"); - } - } - } - html.append(""); - - resp.addHeader("content-type", "text/html;charset=UTF-8"); - try { - resp.getWriter().write(html.toString()); - } catch (IOException e) { - logger.warn("return html failed with uri syntax error", e); - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ConsoleCommandExtension.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ConsoleCommandExtension.java index bcff05367b..b9f4fa4576 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ConsoleCommandExtension.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ConsoleCommandExtension.java @@ -13,6 +13,8 @@ */ package org.smarthomej.binding.amazonechocontrol.internal; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_ID; + import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -41,8 +43,7 @@ public class ConsoleCommandExtension extends AbstractConsoleCommandExtension { @Activate public ConsoleCommandExtension(@Reference AmazonEchoControlHandlerFactory handlerFactory) { - super("amazonechocontrol", "Manage the AmazonEchoControl account"); - + super(BINDING_ID, "Manage the AmazonEchoControl account"); this.handlerFactory = handlerFactory; } @@ -84,7 +85,7 @@ private void resetAccount(Console console, String accountId) { .filter(handler -> handler.getThing().getUID().getId().equals(accountId)).findAny(); if (accountHandler.isPresent()) { console.println("Resetting account '" + accountId + "'"); - accountHandler.get().setConnection(null); + accountHandler.get().resetConnection(true); } else { console.println("Account '" + accountId + "' not found."); } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUri.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUri.java new file mode 100644 index 0000000000..c2f7acb429 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUri.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; + +import static org.eclipse.jetty.util.StringUtil.isNotBlank; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlServlet.SERVLET_PATH; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ServletUri} is the record for structured handling of the servlet URI + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault({}) +public record ServletUri(String account, String request) { + private static final Pattern URI_PART_PATTERN = Pattern.compile(SERVLET_PATH + "(?:/(\\w+)(/.+)?)?/?"); + + public String buildFor(String uri) { + if (uri.startsWith("/")) { + return SERVLET_PATH + "/" + account() + uri; + } else { + return SERVLET_PATH + "/" + account() + "/" + uri; + } + } + + public static @Nullable ServletUri fromFullUri(@Nullable String requestUri) throws IllegalArgumentException { + if (requestUri == null) { + return null; + } + Matcher matcher = URI_PART_PATTERN.matcher(requestUri); + if (!matcher.matches()) { + return null; + } + return new ServletUri(isNotBlank(matcher.group(1)) ? matcher.group(1) : "", + isNotBlank(matcher.group(2)) ? matcher.group(2) : ""); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/AmazonHandlerCallback.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/AmazonHandlerCallback.java deleted file mode 100644 index 34d68452d0..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/AmazonHandlerCallback.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.channelhandler; - -import java.io.IOException; -import java.net.URISyntaxException; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.types.State; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; - -/** - * The {@link AmazonHandlerCallback} is used from ChannelHandlers to communicate - * with the thing - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public interface AmazonHandlerCallback { - - void updateChannelState(String channelId, State state); - - void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title, - @Nullable Integer volume) throws IOException, URISyntaxException; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java deleted file mode 100644 index b248cfec0d..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.channelhandler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; - -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 - */ -@NonNullByDefault -public abstract class ChannelHandler { - - public abstract boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command) - throws ConnectionException; - - protected final AmazonHandlerCallback callback; - protected final Gson gson; - private final Logger logger; - - protected ChannelHandler(AmazonHandlerCallback callback, Gson gson) { - this.logger = LoggerFactory.getLogger(this.getClass()); - this.callback = callback; - 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.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java deleted file mode 100644 index cf7079bfa3..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.channelhandler; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.library.types.StringType; -import org.openhab.core.types.Command; -import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.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 - */ -@NonNullByDefault -public class ChannelHandlerAnnouncement extends ChannelHandler { - - private static final String CHANNEL_NAME = "announcement"; - - public ChannelHandlerAnnouncement(AmazonHandlerCallback callback, Gson gson) { - super(callback, gson); - } - - @Override - public boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command) - throws ConnectionException { - try { - if (channelId.equals(CHANNEL_NAME)) { - if (command instanceof StringType) { - String commandValue = command.toFullString(); - String body = commandValue; - String title = null; - String speak = commandValue; - Integer volume = null; - if (commandValue.startsWith("{") && commandValue.endsWith("}")) { - try { - AnnouncementRequestJson request = parseJson(commandValue, AnnouncementRequestJson.class); - if (request != null) { - speak = request.speak; - if (speak == null || speak.length() == 0) { - speak = "."; // blank generates a beep - } - volume = request.volume; - title = request.title; - body = request.body; - if (body == null) { - body = speak; - } - Boolean sound = request.sound; - if (sound != null) { - if (!sound && !speak.startsWith("")) { - speak = "" + speak + ""; - } - if (sound && speak.startsWith("")) { - body = "Error: The combination of sound and speak in SSML syntax is not allowed"; - title = "Error"; - speak = "Error: The combination of sound and speak in SSML syntax is not allowed"; - } - } - if (" ".equals(speak)) { - volume = -1; // Do not change volume - } - } - } catch (JsonSyntaxException e) { - body = "Invalid Json." + e.getLocalizedMessage(); - title = "Error"; - speak = "" + body + ""; - body = e.getLocalizedMessage(); - } - } - callback.startAnnouncement(device, speak, Objects.requireNonNullElse(body, ""), title, volume); - } - refreshChannel(); - } - } catch (IOException | URISyntaxException e) { - throw new ConnectionException(e.getMessage(), e); - } - return false; - } - - private void refreshChannel() { - callback.updateChannelState(CHANNEL_NAME, new StringType("")); - } - - private static class AnnouncementRequestJson { - public @Nullable Boolean sound; - public @Nullable String title; - public @Nullable String body; - public @Nullable String speak; - public @Nullable Integer volume; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java deleted file mode 100644 index 13f801080b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.channelhandler; - -import java.time.LocalDateTime; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.library.types.StringType; -import org.openhab.core.types.Command; -import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; - -import com.google.gson.Gson; - -/** - * The {@link ChannelHandlerSendMessage} is responsible for the announcement - * channel - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class ChannelHandlerSendMessage extends ChannelHandler { - - private static final String CHANNEL_NAME = "sendMessage"; - private @Nullable AccountJson accountJson; - private int lastMessageId = 1000; - - public ChannelHandlerSendMessage(AmazonHandlerCallback thingHandler, Gson gson) { - super(thingHandler, gson); - } - - @Override - public boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command) - throws ConnectionException { - if (channelId.equals(CHANNEL_NAME)) { - if (command instanceof StringType) { - String commandValue = ((StringType) command).toFullString(); - String baseUrl = "https://alexa-comms-mobile-service." + connection.getAmazonSite(); - - AccountJson currentAccountJson = this.accountJson; - if (currentAccountJson == null) { - String accountResult = connection.makeRequestAndReturnString(baseUrl + "/accounts"); - AccountJson @Nullable [] accountsJson = gson.fromJson(accountResult, AccountJson[].class); - if (accountsJson == null) { - return false; - } - for (AccountJson accountJson : accountsJson) { - Boolean signedInUser = accountJson.signedInUser; - if (signedInUser != null && signedInUser) { - this.accountJson = accountJson; - currentAccountJson = accountJson; - break; - } - } - } - if (currentAccountJson == null) { - return false; - } - String commsId = currentAccountJson.commsId; - if (commsId == null) { - return false; - } - String senderCommsId = commsId; - String receiverCommsId = commsId; - - SendConversationJson conversationJson = new SendConversationJson(); - conversationJson.conversationId = "amzn1.comms.messaging.id.conversationV2~31e6fe8f-8b0c-4e84-a1e4-80030a09009b"; - conversationJson.clientMessageId = java.util.UUID.randomUUID().toString(); - conversationJson.messageId = lastMessageId++; - conversationJson.sender = senderCommsId; - conversationJson.time = LocalDateTime.now().toString(); - conversationJson.payload.text = commandValue; - - String sendConversationBody = this.gson.toJson(new SendConversationJson[] { conversationJson }); - String sendUrl = baseUrl + "/users/" + senderCommsId + "/conversations/" + receiverCommsId - + "/messages"; - connection.makeRequestAndReturnString("POST", sendUrl, sendConversationBody, true, Map.of()); - } - refreshChannel(); - } - return false; - } - - private void refreshChannel() { - callback.updateChannelState(CHANNEL_NAME, new StringType("")); - } - - @SuppressWarnings("unused") - private static class AccountJson { - public @Nullable String commsId; - public @Nullable String directedId; - public @Nullable String phoneCountryCode; - public @Nullable String phoneNumber; - public @Nullable String firstName; - public @Nullable String lastName; - public @Nullable String phoneticFirstName; - public @Nullable String phoneticLastName; - public @Nullable String commsProvisionStatus; - public @Nullable Boolean isChild; - public @Nullable Boolean signedInUser; - public @Nullable Boolean commsProvisioned; - public @Nullable Boolean speakerProvisioned; - } - - @SuppressWarnings("unused") - private static class SendConversationJson { - public @Nullable String conversationId; - public @Nullable String clientMessageId; - public @Nullable Integer messageId; - public @Nullable String time; - public @Nullable String sender; - public String type = "message/text"; - public Payload payload = new Payload(); - public Integer status = 1; - - private static class Payload { - public @Nullable String text; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/AnnouncementWrapper.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/AnnouncementWrapper.java index c7377418e3..a3e519c790 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/AnnouncementWrapper.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/AnnouncementWrapper.java @@ -17,7 +17,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.AnnouncementContentTO; /** * The {@link AnnouncementWrapper} is a wrapper for announcement instructions @@ -26,7 +27,7 @@ */ @NonNullByDefault public class AnnouncementWrapper { - private final List devices = new ArrayList<>(); + private final List devices = new ArrayList<>(); private final List<@Nullable Integer> ttsVolumes = new ArrayList<>(); private final List<@Nullable Integer> standardVolumes = new ArrayList<>(); @@ -40,13 +41,13 @@ public AnnouncementWrapper(String speak, String bodyText, @Nullable String title this.title = title; } - public void add(JsonDevices.Device device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { + public void add(DeviceTO device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { devices.add(device); ttsVolumes.add(ttsVolume); standardVolumes.add(standardVolume); } - public List getDevices() { + public List getDevices() { return devices; } @@ -69,4 +70,14 @@ public String getBodyText() { public List<@Nullable Integer> getStandardVolumes() { return standardVolumes; } + + public AnnouncementContentTO toAnnouncementTO() { + AnnouncementContentTO announcement = new AnnouncementContentTO(); + announcement.display.body = bodyText; + String title = this.title; + announcement.display.title = (title == null || title.isBlank()) ? "openHAB" : title; + announcement.speak.value = speak; + announcement.speak.type = (speak.startsWith("") && speak.endsWith("")) ? "ssml" : "text"; + return announcement; + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/Connection.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/Connection.java index db8664317b..5cac36ccfa 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/Connection.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/Connection.java @@ -13,22 +13,25 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.connection; +import static org.eclipse.jetty.http.HttpStatus.NO_CONTENT_204; +import static org.eclipse.jetty.http.HttpStatus.OK_200; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CAPABILITY_REGISTRATION; + import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.CookieManager; +import java.net.CookieStore; import java.net.HttpCookie; import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Base64; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -36,7 +39,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Scanner; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; @@ -46,75 +49,68 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import java.util.zip.GZIPInputStream; - -import javax.net.ssl.HttpsURLConnection; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http.HttpHeader; 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.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAnnouncementContent; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAnnouncementTarget; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAscendingAlarm; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAscendingAlarm.AscendingAlarmModel; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAutomation; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAutomation.Payload; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBootstrapResult; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBootstrapResult.Authentication; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCustomerHistoryRecords; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCustomerHistoryRecords.CustomerHistoryRecord; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState.DeviceNotificationState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDoNotDisturb; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDoNotDisturb.DoNotDisturbDeviceStatus; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonEnabledFeeds; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonEqualizer; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonExchangeTokenResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonExchangeTokenResponse.Cookie; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonFeed; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMediaState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMusicProvider; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNetworkDetails; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationRequest; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationSounds; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationsResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaySearchPhraseOperationPayload; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayValidationResult; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaylists; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppRequest; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Bearer; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.DeviceInfo; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Extensions; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.MacDms; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Response; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Success; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Tokens; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRenewTokenResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonStartRoutineRequest; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonUsersMeResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonWakeWords; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonWakeWords.WakeWord; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonWebSiteCookie; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.AscendingAlarmModelTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.CookieTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceNotificationStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DoNotDisturbDeviceStatusTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EnabledFeedTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EnabledFeedsTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EqualizerTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlaySearchPhraseTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.TOMapper; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.AnnouncementTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.AuthRegisterTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.BehaviorOperationValidateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.ExchangeTokenTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.StartRoutineTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AscendingAlarmModelsTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AuthRegisterResponseTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AuthRegisterTokensTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AuthTokenTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AutomationPayloadTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AutomationTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AutomationTriggerTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStatesTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BootstrapAuthenticationTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BootstrapTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordsTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.DeviceListTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.DeviceNotificationStatesTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.DoNotDisturbDeviceStatusesTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.EndpointTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.ListMediaSessionTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MediaSessionTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MusicProviderTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.NotificationListResponseTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.NotificationSoundResponseTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.PlayerStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.UsersMeTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.WakeWordTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.WakeWordsTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice; +import org.smarthomej.binding.amazonechocontrol.internal.util.HttpRequestBuilder; import org.unbescape.json.JsonEscape; import org.unbescape.json.JsonEscapeLevel; import org.unbescape.json.JsonEscapeType; @@ -123,40 +119,35 @@ import org.unbescape.xml.XmlEscapeType; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSyntaxException; /** - * The {@link Connection} is responsible for the connection to the amazon server - * and handling of the commands + * The {@link Connection} is responsible for the connection to the amazon server and handling of the commands * * @author Michael Geramb - Initial contribution + * @author Jan N. Klug - Refactored to use jetty client, add {@link HttpRequestBuilder} */ @NonNullByDefault public class Connection { private static final String THING_THREADPOOL_NAME = "thingHandler"; private static final long EXPIRES_IN = 432000; // five days - private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); - private static final String USER_AGENT = "AmazonWebView/Amazon Alexa/2.2.443692.0/iOS/14.8/iPhone"; private final Logger logger = LoggerFactory.getLogger(Connection.class); protected final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THING_THREADPOOL_NAME); - private final CookieManager cookieManager = new CookieManager(); private final Gson gson; - private final Gson gsonWithNullSerialization; - private final LoginData loginData; - private String alexaServer = "https://alexa.amazon.com"; + private LoginData loginData; + private CookieManager cookieManager = new CookieManager(); + private final HttpRequestBuilder requestBuilder; private @Nullable Date verifyTime; - private long renewTime = 0; + private long connectionExpiryTime = 0; + private long accessTokenExpiryTime = 0; private @Nullable String customerName; - private @Nullable MacDms macDms; + private @Nullable String accessToken; private final Map announcements = Collections.synchronizedMap(new LinkedHashMap<>()); private final Map textToSpeeches = Collections.synchronizedMap(new LinkedHashMap<>()); @@ -177,38 +168,24 @@ private enum TimerType { TEXT_COMMAND } - public Connection(@Nullable Connection oldConnection, Gson gson) { + public Connection(@Nullable Connection oldConnection, Gson gson, HttpClient httpClient) { this.gson = gson; + + this.requestBuilder = new HttpRequestBuilder(httpClient, cookieManager, gson); if (oldConnection != null) { - this.loginData = new LoginData(cookieManager, oldConnection.getDeviceId(), oldConnection.getFrc(), - oldConnection.getSerial()); + LoginData oldLoginData = oldConnection.getLoginData(); + this.loginData = new LoginData(cookieManager, oldLoginData.getDeviceId(), oldLoginData.getFrc(), + oldLoginData.getSerial()); } else { this.loginData = new LoginData(cookieManager); } - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonWithNullSerialization = gsonBuilder.create(); - replaceTimer(TimerType.DEVICES, scheduler.scheduleWithFixedDelay(this::handleExecuteSequenceNode, 0, 500, TimeUnit.MILLISECONDS)); } - private void setAmazonSite(@Nullable String amazonSite) { - String correctedAmazonSite = Objects.requireNonNullElse(amazonSite, "amazon.com").toLowerCase(); - if (correctedAmazonSite.startsWith("http://")) { - correctedAmazonSite = correctedAmazonSite.substring(7); - } - if (correctedAmazonSite.startsWith("https://")) { - correctedAmazonSite = correctedAmazonSite.substring(8); - } - if (correctedAmazonSite.startsWith("www.")) { - correctedAmazonSite = correctedAmazonSite.substring(4); - } - if (correctedAmazonSite.startsWith("alexa.")) { - correctedAmazonSite = correctedAmazonSite.substring(6); - } - this.loginData.amazonSite = correctedAmazonSite; - this.alexaServer = "https://alexa." + correctedAmazonSite; + public HttpRequestBuilder getRequestBuilder() { + return requestBuilder; } public LoginData getLoginData() { @@ -216,40 +193,20 @@ public LoginData getLoginData() { return loginData; } - public @Nullable Date tryGetVerifyTime() { + public @Nullable Date getVerifyTime() { return verifyTime; } - public String getFrc() { - return loginData.frc; + public String getRetailDomain() { + return loginData.getRetailDomain(); } - public String getSerial() { - return loginData.serial; - } - - public String getDeviceId() { - return loginData.deviceId; - } - - public String getAmazonSite() { - return loginData.amazonSite; + public String getRetailUrl() { + return loginData.getRetailUrl(); } public String getAlexaServer() { - return alexaServer; - } - - public @Nullable MacDms getMacDms() { - return this.macDms; - } - - public String getDeviceName() { - return this.loginData.deviceName; - } - - public String getCustomerId() { - return Objects.requireNonNullElse(loginData.accountCustomerId, "Unknown"); + return this.loginData.getWebsiteApiUrl(); } public String getCustomerName() { @@ -261,485 +218,233 @@ public boolean isSequenceNodeQueueRunning() { (queueObjects) -> (queueObjects.stream().anyMatch(queueObject -> queueObject.future != null))); } - public boolean tryRestoreLogin(@Nullable String data, @Nullable String overloadedDomain) { - Date loginTime = tryRestoreSessionData(data, overloadedDomain); - if (loginTime != null) { - try { + public boolean restoreLogin(@Nullable String data, @Nullable String overloadedDomain) { + try { + // verify stored data + if (data != null && !data.isEmpty() && loginData.deserialize(data)) { + if (overloadedDomain != null) { + loginData.setRetailDomain(overloadedDomain); + } + renewTokens(); if (verifyLogin()) { - this.loginData.loginTime = loginTime; return true; } - } catch (ConnectionException e) { - // no action } + } catch (ConnectionException e) { + // no action } + this.loginData.setLoginTime(null); return false; } - private @Nullable Date tryRestoreSessionData(@Nullable String data, @Nullable String overloadedDomain) { - // verify stored data - if (data == null || data.isEmpty() || !loginData.deserialize(data)) { - return null; - } - - if (overloadedDomain != null) { - loginData.amazonSite = overloadedDomain; - } - - setAmazonSite(loginData.amazonSite); - + private boolean tryGetBootstrap() { try { - checkRenewSession(); - - String accountCustomerId = this.loginData.accountCustomerId; - if (accountCustomerId == null || accountCustomerId.isEmpty()) { - List devices = this.getDeviceList(); - accountCustomerId = devices.stream().filter(device -> loginData.serial.equals(device.serialNumber)) - .findAny().map(device -> device.deviceOwnerCustomerId).orElse(null); - if (accountCustomerId == null || accountCustomerId.isEmpty()) { - accountCustomerId = devices.stream().filter(device -> "This Device".equals(device.accountName)) - .findAny().map(device -> { - loginData.serial = Objects.requireNonNullElse(device.serialNumber, loginData.serial); - return device.deviceOwnerCustomerId; - }).orElse(null); - } - this.loginData.accountCustomerId = accountCustomerId; + BootstrapTO result = requestBuilder.get(getAlexaServer() + "/api/bootstrap").retry(false).redirect(false) + .syncSend(BootstrapTO.class); + BootstrapAuthenticationTO authentication = result.authentication; + if (authentication != null && authentication.authenticated) { + this.customerName = authentication.customerName; + this.loginData.setAccountCustomerId(authentication.customerId); + return authentication.authenticated; } - } catch (URISyntaxException | ConnectionException e) { - logger.debug("Getting account customer Id failed", e); + } catch (ConnectionException e) { + logger.debug("Bootstrapping failed", e); } - return loginData.loginTime; + return false; } - private @Nullable Authentication tryGetBootstrap() throws ConnectionException { - HttpsURLConnection connection = makeRequest("GET", alexaServer + "/api/bootstrap", null, false, false, Map.of(), - 0); - String contentType = connection.getContentType(); + public boolean registerConnectionAsApp(String accessToken) { try { - if (connection.getResponseCode() == 200 && contentType != null - && contentType.toLowerCase().startsWith("application/json")) { - String bootstrapResultJson = convertStream(connection); - JsonBootstrapResult result = parseJson(bootstrapResultJson, JsonBootstrapResult.class); - if (result != null) { - Authentication authentication = result.authentication; - if (authentication != null && authentication.authenticated) { - this.customerName = authentication.customerName; - if (this.loginData.accountCustomerId == null) { - this.loginData.accountCustomerId = authentication.customerId; - } - return authentication; - } - } - } - } catch (JsonSyntaxException | IllegalStateException e) { - logger.info("No valid json received", e); - return null; - } catch (IOException e) { - throw new ConnectionException(e.getMessage(), e); - } - return null; - } - - public String convertStream(HttpsURLConnection connection) throws ConnectionException { - try (InputStream input = connection.getInputStream()) { - if (input == null) { - return ""; - } - - InputStream readerStream; - if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) { - readerStream = new GZIPInputStream(input); - } else { - readerStream = input; - } - String contentType = connection.getContentType(); - String charSet = StandardCharsets.UTF_8.name(); - - if (contentType != null) { - Matcher m = CHARSET_PATTERN.matcher(contentType); - if (m.find()) { - String foundCharset = m.group(1).trim().toUpperCase(); - if (!foundCharset.isEmpty()) { - charSet = foundCharset; - } - } - } - - Scanner inputScanner = new Scanner(readerStream, charSet); - Scanner scannerWithoutDelimiter = inputScanner.useDelimiter("\\A"); - String result = scannerWithoutDelimiter.hasNext() ? scannerWithoutDelimiter.next() : ""; - inputScanner.close(); - scannerWithoutDelimiter.close(); - input.close(); + List webSiteCookies = cookieManager.getCookieStore().get(URI.create("https://www.amazon.com")) + .stream().map(TOMapper::mapCookie).toList(); - return result; - } catch (IOException e) { - throw new ConnectionException(e.getMessage(), e); - } - } + AuthRegisterTO registerAppRequest = new AuthRegisterTO(); + registerAppRequest.registrationData.deviceSerial = loginData.getSerial(); + registerAppRequest.authData.accessToken = accessToken; + registerAppRequest.userContextMap = Map.of("frc", loginData.getFrc()); + registerAppRequest.cookies.webSiteCookies = webSiteCookies; - public String makeRequestAndReturnString(String url) throws ConnectionException { - return makeRequestAndReturnString("GET", url, null, false, Map.of()); - } + AuthRegisterResponseTO registerAppResponse = requestBuilder.post("https://api.amazon.com/auth/register") + .withContent(registerAppRequest) + .withHeaders(Map.of("x-amzn-identity-auth-domain", "api.amazon.com")).syncSend( + org.smarthomej.binding.amazonechocontrol.internal.dto.response.AuthRegisterTO.class).response; - public String makeRequestAndReturnString(String requestMethod, String url, @Nullable String postData, boolean json, - Map customHeaders) throws ConnectionException { - HttpsURLConnection connection = makeRequest(requestMethod, url, postData, json, true, customHeaders, 3); - String result = convertStream(connection); - logger.trace("Result of {} {}:{}", requestMethod, url, result); - return result; - } + AuthRegisterTokensTO tokens = registerAppResponse.success.tokens; + String refreshToken = tokens.bearer.refreshToken; - public HttpsURLConnection makeRequest(String requestMethod, String url, @Nullable String postData, boolean json, - boolean autoredirect, Map customHeaders, int badRequestRepeats) throws ConnectionException { - String currentUrl = url; - int redirectCounter = 0; - int retryCounter = 0; - // loop for handling redirect and bad request, using automatic redirect is not - // possible, because all response headers must be catched - while (true) { - int code; - HttpsURLConnection connection = null; - try { - logger.debug("Make request to {}", url); - connection = (HttpsURLConnection) new URL(currentUrl).openConnection(); - connection.setRequestMethod(requestMethod); - connection.setRequestProperty("Accept-Language", "en-US"); - if (!customHeaders.containsKey("User-Agent")) { - connection.setRequestProperty("User-Agent", USER_AGENT); - } - connection.setRequestProperty("Accept-Encoding", "gzip"); - connection.setRequestProperty("DNT", "1"); - connection.setRequestProperty("Upgrade-Insecure-Requests", "1"); - for (Map.Entry header : customHeaders.entrySet()) { - if (!header.getValue().isEmpty()) { - connection.setRequestProperty(header.getKey(), header.getValue()); - } - } - connection.setInstanceFollowRedirects(false); - - // add cookies - URI uri = connection.getURL().toURI(); - - if (!customHeaders.containsKey("Cookie")) { - StringBuilder cookieHeaderBuilder = new StringBuilder(); - for (HttpCookie cookie : cookieManager.getCookieStore().get(uri)) { - if (cookieHeaderBuilder.length() > 0) { - cookieHeaderBuilder.append(";"); - } - cookieHeaderBuilder.append(cookie.getName()); - cookieHeaderBuilder.append("="); - cookieHeaderBuilder.append(cookie.getValue()); - if (cookie.getName().equals("csrf")) { - connection.setRequestProperty("csrf", cookie.getValue()); - } - - } - if (cookieHeaderBuilder.length() > 0) { - String cookies = cookieHeaderBuilder.toString(); - connection.setRequestProperty("Cookie", cookies); - } - } - if (postData != null) { - logger.debug("{}: {}", requestMethod, postData); - // post data - byte[] postDataBytes = postData.getBytes(StandardCharsets.UTF_8); - int postDataLength = postDataBytes.length; + this.loginData.setRefreshToken(refreshToken); - connection.setFixedLengthStreamingMode(postDataLength); + if (refreshToken == null || refreshToken.isBlank()) { + logger.warn("Could not determine refreshToken while trying to register as app."); + return false; + } - if (json) { - connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - } else { - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - } - connection.setRequestProperty("Content-Length", Integer.toString(postDataLength)); - if ("POST".equals(requestMethod)) { - connection.setRequestProperty("Expect", "100-continue"); - } + exchangeToken(getRetailDomain()); + // Check which is the owner domain + UsersMeTO usersMeResponse = requestBuilder.get("https://alexa.amazon.com/api/users/me?platform=ios&version=" + + AmazonEchoControlBindingConstants.API_VERSION).syncSend(UsersMeTO.class); - connection.setDoOutput(true); - OutputStream outputStream = connection.getOutputStream(); - outputStream.write(postDataBytes); - outputStream.close(); - } - // handle result - code = connection.getResponseCode(); - String location = null; - - // handle response headers - Map<@Nullable String, List> headerFields = connection.getHeaderFields(); - for (Map.Entry<@Nullable String, List> header : headerFields.entrySet()) { - String key = header.getKey(); - if (key != null && !key.isEmpty()) { - if ("Set-Cookie".equalsIgnoreCase(key)) { - // store cookie - for (String cookieHeader : header.getValue()) { - if (!cookieHeader.isEmpty()) { - List cookies = HttpCookie.parse(cookieHeader); - for (HttpCookie cookie : cookies) { - cookieManager.getCookieStore().add(uri, cookie); - } - } - } - } - if ("Location".equalsIgnoreCase(key)) { - // get redirect location - location = header.getValue().get(0); - if (!location.isEmpty()) { - location = uri.resolve(location).toString(); - // check for https - if (location.toLowerCase().startsWith("http://")) { - // always use https - location = "https://" + location.substring(7); - logger.debug("Redirect corrected to {}", location); - } - } - } - } - } - if (code == 200) { - logger.debug("Call to {} succeeded", url); - return connection; - } else if (code == 301 || code == 302 && location != null) { - logger.debug("Redirected to {}", location); - redirectCounter++; - if (redirectCounter > 30) { - throw new ConnectionException("Too many redirects"); - } - currentUrl = location; - if (autoredirect) { - continue; // repeat with new location - } - return connection; - } else if (code == 400 && "QUEUE_EXPIRED".equals(connection.getHeaderField("x-amzn-error"))) { - // handle queue expired - throw new ConnectionException("Queue expired"); - } else { - logger.debug("Retry call to {}", url); - retryCounter++; - if (retryCounter > badRequestRepeats) { - throw new ConnectionException(requestMethod + " url '" + url + "' failed with code " + code - + ": " + connection.getResponseMessage()); - } - Thread.sleep(2000); - } - } catch (Exception e) { - if (connection != null) { - connection.disconnect(); - } - logger.debug("Request to url '{}' fails:", url, e); - throw new ConnectionException("Request failed", e); + // Switch to owner domain + exchangeToken(usersMeResponse.marketPlaceDomainName); + EndpointTO endpoints = requestBuilder.get("https://alexa.amazon.com/api/endpoints") + .syncSend(EndpointTO.class); + this.loginData.setRetailDomain(endpoints.retailDomain); + this.loginData.setWebsiteApiUrl(endpoints.websiteApiUrl); + this.loginData.setRetailUrl(endpoints.retailUrl); + + HttpRequestBuilder.HttpResponse response = requestBuilder + .put("https://api.amazonalexa.com/v1/devices/@self/capabilities") + .withContent(CAPABILITY_REGISTRATION) + .withHeaders(Map.of(HttpHeader.AUTHORIZATION.toString(), "Bearer " + tokens.bearer.accessToken)) + .retry(false).syncSend(); + if (response.statusCode() != NO_CONTENT_204 && response.statusCode() != OK_200) { + logger.warn("Registering capabilities failed, HTTP/2 stream will not work"); } - } - } - public void registerConnectionAsApp(String oAutRedirectUrl) - throws ConnectionException, IOException, URISyntaxException, InterruptedException { - URI oAutRedirectUri = new URI(oAutRedirectUrl); + tryGetBootstrap(); + this.loginData.setDeviceName(registerAppResponse.success.extensions.deviceInfo.deviceName); - Map queryParameters = new LinkedHashMap<>(); - String query = oAutRedirectUri.getQuery(); - String[] pairs = query.split("&"); - for (String pair : pairs) { - int idx = pair.indexOf("="); - queryParameters.put(URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()), - URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())); + return true; + } catch (Exception e) { + logger.warn("Registering as app failed: {}", e.getMessage()); + logout(false); + return false; } - String accessToken = queryParameters.get("openid.oa2.access_token"); - - reRegisterConnectionAsApp(accessToken); } - private void reRegisterConnectionAsApp(@Nullable String accessToken) throws ConnectionException { - List webSiteCookies = new ArrayList<>(); - for (HttpCookie cookie : getSessionCookies("https://www.amazon.com")) { - webSiteCookies.add(new JsonWebSiteCookie(cookie.getName(), cookie.getValue())); - } + private void exchangeToken(String cookieDomain) throws ConnectionException { + this.connectionExpiryTime = 0; + String cookiesJson = "{\"cookies\":{\"." + cookieDomain + "\":[]}}"; + String cookiesBase64 = Base64.getEncoder().encodeToString(cookiesJson.getBytes()); - JsonRegisterAppRequest registerAppRequest = new JsonRegisterAppRequest(loginData.serial, accessToken, - loginData.frc, webSiteCookies); - String registerAppRequestJson = gson.toJson(registerAppRequest); + String exchangePostData = "di.os.name=iOS" // + + "&app_version=" + AmazonEchoControlBindingConstants.API_VERSION // + + "&domain=." + getRetailDomain() // + + "&source_token=" + URLEncoder.encode(this.loginData.getRefreshToken(), StandardCharsets.UTF_8) // + + "&requested_token_type=auth_cookies" // + + "&source_token_type=refresh_token" // + + "&di.hw.version=iPhone" // + + "&di.sdk.version=" + AmazonEchoControlBindingConstants.DI_SDK_VERSION // + + "&cookies=" + cookiesBase64 // + + "&app_name=Amazon%20Alexa" // + + "&di.os.version=" + AmazonEchoControlBindingConstants.DI_OS_VERSION; - String registerAppResultJson = makeRequestAndReturnString("POST", "https://api.amazon.com/auth/register", - registerAppRequestJson, true, Map.of("x-amzn-identity-auth-domain", "api.amazon.com")); - JsonRegisterAppResponse registerAppResponse = parseJson(registerAppResultJson, JsonRegisterAppResponse.class); + String url = getRetailUrl() + "/ap/exchangetoken"; - if (registerAppResponse == null) { - throw new ConnectionException("Error: No response received from register application"); - } - Response response = registerAppResponse.response; - if (response == null) { - throw new ConnectionException("Error: No response received from register application"); - } - Success success = response.success; - if (success == null) { - throw new ConnectionException("Error: No success received from register application"); - } - Tokens tokens = success.tokens; - if (tokens == null) { - throw new ConnectionException("Error: No tokens received from register application"); - } - Bearer bearer = tokens.bearer; - if (bearer == null) { - throw new ConnectionException("Error: No bearer received from register application"); - } - String refreshToken = bearer.refreshToken; + ExchangeTokenTO exchangeToken = requestBuilder.post(url).withContent(exchangePostData).withHeader("Cookie", "") + .syncSend(ExchangeTokenTO.class); - this.macDms = tokens.macDms; + CookieStore cookieStore = cookieManager.getCookieStore(); + exchangeToken.response.tokens.cookies + .forEach((domain, cookies) -> cookies.stream().map(cookie -> TOMapper.mapCookie(cookie, domain)) + .forEach(httpCookie -> cookieStore.add(null, httpCookie))); - this.loginData.refreshToken = refreshToken; - if (refreshToken == null || refreshToken.isEmpty()) { - throw new ConnectionException("Error: No refresh token received"); - } - try { - exchangeToken(); - // Check which is the owner domain - String usersMeResponseJson = makeRequestAndReturnString( - "https://alexa.amazon.com/api/users/me?platform=ios&version=2.2.443692.0"); - JsonUsersMeResponse usersMeResponse = parseJson(usersMeResponseJson, JsonUsersMeResponse.class); - if (usersMeResponse == null) { - throw new IllegalStateException(); - } - URI uri = new URI(usersMeResponse.marketPlaceDomainName); - String host = uri.getHost(); - - // Switch to owner domain - setAmazonSite(host); - exchangeToken(); - tryGetBootstrap(); - } catch (Exception e) { - logout(); - throw new ConnectionException(e.getMessage(), e); + if (!verifyLogin()) { + throw new ConnectionException("Verify login failed after token exchange"); } - String deviceName = null; - Extensions extensions = success.extensions; - if (extensions != null) { - DeviceInfo deviceInfo = extensions.deviceInfo; - if (deviceInfo != null) { - deviceName = deviceInfo.deviceName; + // renew at 80% expired + this.connectionExpiryTime = System.currentTimeMillis() + (long) (Connection.EXPIRES_IN * 1000d / 0.8d); + } + + public String getAccessToken() throws ConnectionException { + String accessToken = this.accessToken; + if (accessToken == null) { + throw new ConnectionException("accessToken not set"); + } + return accessToken; + } + + /** + * Check if tokens need to be renewed + *

+ * The {@link #accessToken} is renewed when the current nextAlarmTime is above + * {@link #accessTokenExpiryTime}, additionally the session tokens/cookies are renewed when the current + * nextAlarmTime is + * above {@link #connectionExpiryTime} + * + * @return {@code true} when the session tokens have been renewed, {@code false} otherwise + * @throws ConnectionException when an error occurred + */ + public boolean renewTokens() throws ConnectionException { + if (System.currentTimeMillis() >= this.accessTokenExpiryTime) { + String renewTokenPostData = "app_name=Amazon%20Alexa" // + + "&app_version=" + AmazonEchoControlBindingConstants.API_VERSION // + + "&di.sdk.version=" + AmazonEchoControlBindingConstants.DI_SDK_VERSION // + + "&source_token=" + URLEncoder.encode(loginData.getRefreshToken(), StandardCharsets.UTF_8) // + + "&package_name=com.amazon.echo" // + + "&di.hw.version=iPhone" // + + "&platform=iOS" // + + "&requested_token_type=access_token"// + + "&source_token_type=refresh_token" // + + "&di.os.name=iOS" // + + "&di.os.version=" + AmazonEchoControlBindingConstants.DI_OS_VERSION // + + "¤t_version=6.12.4"; + + AuthTokenTO tokenResponse = requestBuilder.post("https://api.amazon.com/auth/token") + .withContent(renewTokenPostData).syncSend(AuthTokenTO.class); + + String accessToken = tokenResponse.accessToken; + this.accessToken = accessToken; + if (accessToken == null) { + throw new ConnectionException("Failed to renew access token, no token received."); } - } - this.loginData.deviceName = Objects.requireNonNullElse(deviceName, "Unknown"); - } - - private void exchangeToken() throws ConnectionException { - this.renewTime = 0; - String cookiesJson = "{\"cookies\":{\"." + getAmazonSite() + "\":[]}}"; - String cookiesBase64 = Base64.getEncoder().encodeToString(cookiesJson.getBytes()); + // renew at 80% expired + this.accessTokenExpiryTime = System.currentTimeMillis() + (long) ((tokenResponse.expiresIn * 1000.0) / 0.8); - String exchangePostData = "di.os.name=iOS&app_version=2.2.443692.0&domain=." + getAmazonSite() - + "&source_token=" + URLEncoder.encode(this.loginData.refreshToken, StandardCharsets.UTF_8) - + "&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=14.8"; - - String exchangeTokenJson = makeRequestAndReturnString("POST", - "https://www." + getAmazonSite() + "/ap/exchangetoken", exchangePostData, false, Map.of("Cookie", "")); - JsonExchangeTokenResponse exchangeTokenResponse = Objects - .requireNonNull(gson.fromJson(exchangeTokenJson, JsonExchangeTokenResponse.class)); - - org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonExchangeTokenResponse.Response response = exchangeTokenResponse.response; - if (response != null) { - org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonExchangeTokenResponse.Tokens tokens = response.tokens; - if (tokens != null) { - Map cookiesMap = tokens.cookies; - if (cookiesMap != null) { - for (String domain : cookiesMap.keySet()) { - Cookie[] cookies = cookiesMap.get(domain); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie != null) { - HttpCookie httpCookie = new HttpCookie(cookie.name, cookie.value); - httpCookie.setPath(cookie.path); - httpCookie.setDomain(domain); - Boolean secure = cookie.secure; - if (secure != null) { - httpCookie.setSecure(secure); - } - this.cookieManager.getCookieStore().add(null, httpCookie); - } - } - } - } - } - } - } - if (!verifyLogin()) { - throw new ConnectionException("Verify login failed after token exchange"); - } - this.renewTime = (long) (System.currentTimeMillis() + Connection.EXPIRES_IN * 1000d / 0.8d); // start renew at - } - - public boolean checkRenewSession() throws URISyntaxException, ConnectionException { - if (System.currentTimeMillis() >= this.renewTime) { - String renewTokenPostData = "app_name=Amazon%20Alexa&app_version=2.2.443692.0&di.sdk.version=6.10.0&source_token=" - + URLEncoder.encode(loginData.refreshToken, StandardCharsets.UTF_8) - + "&package_name=com.amazon.echo&di.hw.version=iPhone&platform=iOS&requested_token_type=access_token&source_token_type=refresh_token&di.os.name=iOS&di.os.version=14.8¤t_version=6.10.0"; - String renewTokenResponseJson = makeRequestAndReturnString("POST", "https://api.amazon.com/auth/token", - renewTokenPostData, false, Map.of()); - if (this.macDms == null) { - JsonRenewTokenResponse tokenResponse = parseJson(renewTokenResponseJson, JsonRenewTokenResponse.class); - if (tokenResponse == null) { - return false; - } - reRegisterConnectionAsApp(tokenResponse.accessToken); - } else { - exchangeToken(); + if (System.currentTimeMillis() > this.connectionExpiryTime) { + exchangeToken(loginData.getRetailDomain()); } - return true; } return false; } - public boolean getIsLoggedIn() { - return loginData.loginTime != null; + public boolean isLoggedIn() { + return loginData.getLoginTime() != null; } public String getLoginPage() throws ConnectionException { // clear session data - logout(); + logout(false); - logger.debug("Start Login to {}", alexaServer); + logger.debug("Start Login to {}", getAlexaServer()); String mapMdJson = "{\"device_user_dictionary\":[],\"device_registration_data\":{\"software_version\":\"1\"},\"app_identifier\":{\"app_version\":\"2.2.443692\",\"bundle_id\":\"com.amazon.echo\"}}"; String mapMdCookie = Base64.getEncoder().encodeToString(mapMdJson.getBytes()); cookieManager.getCookieStore().add(URI.create("https://www.amazon.com"), new HttpCookie("map-md", mapMdCookie)); - cookieManager.getCookieStore().add(URI.create("https://www.amazon.com"), new HttpCookie("frc", loginData.frc)); - - String loginFormHtml = makeRequestAndReturnString("GET", "https://www.amazon.com" - + "/ap/signin?openid.return_to=https://www.amazon.com/ap/maplanding&openid.assoc_handle=amzn_dp_project_dee_ios&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&pageId=amzn_dp_project_dee_ios&accountStatusPolicy=P1&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns.oa2=http://www.amazon.com/ap/ext/oauth/2&openid.oa2.client_id=device:" - + loginData.deviceId - + "&openid.ns.pape=http://specs.openid.net/extensions/pape/1.0&openid.oa2.response_type=token&openid.ns=http://specs.openid.net/auth/2.0&openid.pape.max_auth_age=0&openid.oa2.scope=device_auth_access", - null, false, Map.of("authority", "www.amazon.com")); - - logger.debug("Received login form {}", loginFormHtml); - return loginFormHtml; + cookieManager.getCookieStore().add(URI.create("https://www.amazon.com"), + new HttpCookie("frc", loginData.getFrc())); + + String url = "https://www.amazon.com/ap/signin" // + + "?openid.return_to=https://www.amazon.com/ap/maplanding" // + + "&openid.assoc_handle=amzn_dp_project_dee_ios" // + + "&openid.identity=http://specs.openid.net/auth/2.0/identifier_select" // + + "&pageId=amzn_dp_project_dee_ios" // + + "&accountStatusPolicy=P1" // + + "&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select" // + + "&openid.mode=checkid_setup" // + + "&openid.ns.oa2=http://www.amazon.com/ap/ext/oauth/2" // + + "&openid.oa2.client_id=device:" + loginData.getDeviceId() // + + "&openid.ns.pape=http://specs.openid.net/extensions/pape/1.0" // + + "&openid.oa2.response_type=token" // + + "&openid.ns=http://specs.openid.net/auth/2.0&openid.pape.max_auth_age=0" // + + "&openid.oa2.scope=device_auth_access"; + + return requestBuilder.get(url).withHeader("authority", "www.amazon.com").syncSend(String.class); } public boolean verifyLogin() throws ConnectionException { - if (this.loginData.refreshToken == null) { + if (this.loginData.getRefreshToken() == null || !tryGetBootstrap()) { + verifyTime = null; return false; } - Authentication authentication = tryGetBootstrap(); - if (authentication != null && authentication.authenticated) { - verifyTime = new Date(); - if (loginData.loginTime == null) { - loginData.loginTime = verifyTime; - } - return true; - } - return false; - } - - public List getSessionCookies(String server) { - try { - return cookieManager.getCookieStore().get(new URI(server)); - } catch (URISyntaxException e) { - return List.of(); + verifyTime = new Date(); + if (loginData.getLoginTime() == null) { + loginData.setLoginTime(verifyTime); } + return true; } // current value in compute can be null @@ -752,12 +457,22 @@ private void replaceTimer(TimerType type, @Nullable ScheduledFuture newTimer) }); } - public void logout() { - cookieManager.getCookieStore().removeAll(); - // reset all members - loginData.refreshToken = null; - loginData.loginTime = null; + public void logout(boolean reset) { + if (reset) { + cookieManager = new CookieManager(); + loginData = new LoginData(cookieManager); + } else { + cookieManager.getCookieStore().removeAll(); + // reset all members + loginData.setRefreshToken(null); + loginData.setLoginTime(null); + } + verifyTime = null; + connectionExpiryTime = 0; + accessTokenExpiryTime = 0; + customerName = null; + accessToken = null; replaceTimer(TimerType.ANNOUNCEMENT, null); announcements.clear(); @@ -778,89 +493,24 @@ public void logout() { })); } - // parser - private @Nullable T parseJson(String json, Class type) throws JsonSyntaxException, IllegalStateException { - try { - // gson.fromJson is non-null if json is non-null and not empty - return gson.fromJson(json, type); - } catch (JsonParseException | IllegalStateException e) { - logger.warn("Parsing json failed: {}", json, e); - throw e; - } - } - // commands and states - public List getWakeWords() { - String json; + public List getWakeWords() { try { - json = makeRequestAndReturnString(alexaServer + "/api/wake-word?cached=true"); - JsonWakeWords wakeWords = parseJson(json, JsonWakeWords.class); - if (wakeWords == null) { - return List.of(); - } - return Objects.requireNonNullElse(wakeWords.wakeWords, List.of()); + return requestBuilder.get(getAlexaServer() + "/api/wake-word?cached=true") + .syncSend(WakeWordsTO.class).wakeWords; } catch (ConnectionException e) { - logger.info("getting wakewords failed", e); + logger.info("Getting wake words failed", e); } return List.of(); } - public List getSmarthomeDeviceList() throws ConnectionException { - try { - String json = makeRequestAndReturnString(alexaServer + "/api/phoenix"); - logger.debug("getSmartHomeDevices result: {}", json); - - JsonNetworkDetails networkDetails = parseJson(json, JsonNetworkDetails.class); - if (networkDetails == null) { - return List.of(); - } - Object jsonObject = gson.fromJson(networkDetails.networkDetail, Object.class); - List result = new ArrayList<>(); - searchSmartHomeDevicesRecursive(jsonObject, result); - - return result; - } catch (ConnectionException e) { - logger.warn("getSmartHomeDevices fails: {}", e.getMessage()); - throw e; - } - } - - private void searchSmartHomeDevicesRecursive(@Nullable Object jsonNode, List devices) { - if (jsonNode instanceof Map) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - Map map = (Map) jsonNode; - if (map.containsKey("entityId") && map.containsKey("friendlyName") && map.containsKey("actions")) { - // device node found, create type element and add it to the results - JsonElement element = gson.toJsonTree(jsonNode); - JsonSmartHomeDevice shd = parseJson(element.toString(), JsonSmartHomeDevice.class); - if (shd != null) { - devices.add(shd); - } - } else if (map.containsKey("applianceGroupName")) { - JsonElement element = gson.toJsonTree(jsonNode); - SmartHomeGroup shg = parseJson(element.toString(), SmartHomeGroup.class); - if (shg != null) { - devices.add(shg); - } - } else { - map.values().forEach(value -> searchSmartHomeDevicesRecursive(value, devices)); - } - } - } - - public List getDeviceList() throws ConnectionException { - JsonDevices devices = Objects.requireNonNull(parseJson(getDeviceListJson(), JsonDevices.class)); - logger.trace("Devices {}", devices.devices); - + public List getDeviceList() throws ConnectionException { + DeviceListTO devices = requestBuilder.get(getAlexaServer() + "/api/devices-v2/device?cached=false") + .syncSend(DeviceListTO.class); // @Nullable because of a limitation of the null-checker, we filter null-serialNumbers before Set<@Nullable String> serialNumbers = ConcurrentHashMap.newKeySet(); return devices.devices.stream().filter(d -> d.serialNumber != null && serialNumbers.add(d.serialNumber)) - .collect(Collectors.toList()); - } - - public String getDeviceListJson() throws ConnectionException { - String json = makeRequestAndReturnString(alexaServer + "/api/devices-v2/device?cached=false"); - return json; + .toList(); } public Map getSmartHomeDeviceStatesJson(Set devices) @@ -892,12 +542,9 @@ public Map getSmartHomeDeviceStatesJson(Set result = new HashMap<>(); for (JsonElement deviceState : deviceStates) { @@ -923,65 +570,55 @@ public Map getSmartHomeDeviceStatesJson(Set getActivities(@Nullable Long startTime, @Nullable Long endTime) { + public List getActivities(long startTime, long endTime) { try { - String json = makeRequestAndReturnString( - "https://" + getAmazonSite() + "/alexa-privacy/apd/rvh/customer-history-records?startTime=" - + (startTime != null ? startTime : "") + "&endTime=" + (endTime != null ? endTime : "") - + "&maxRecordSize=1"); - JsonCustomerHistoryRecords customerHistoryRecords = parseJson(json, JsonCustomerHistoryRecords.class); - if (customerHistoryRecords == null) { - return List.of(); - } - return Objects.requireNonNullElse(customerHistoryRecords.customerHistoryRecords, List.of()); + String url = getRetailUrl() + "/alexa-privacy/apd/rvh/customer-history-records?startTime=" + startTime + + "&endTime=" + endTime + "&maxRecordSize=1"; + CustomerHistoryRecordsTO customerHistoryRecords = requestBuilder.get(url) + .syncSend(CustomerHistoryRecordsTO.class); + return customerHistoryRecords.customerHistoryRecords.stream().sorted(Comparator.comparing(r -> r.timestamp)) + .toList(); } catch (ConnectionException e) { logger.info("getting activities failed", e); } return List.of(); } - public JsonBluetoothStates getBluetoothConnectionStates() { + public List getBluetoothConnectionStates() { try { - String json = makeRequestAndReturnString(alexaServer + "/api/bluetooth?cached=true"); - return Objects.requireNonNullElse(parseJson(json, JsonBluetoothStates.class), new JsonBluetoothStates()); + String url = getAlexaServer() + "/api/bluetooth?cached=true"; + return requestBuilder.get(url).syncSend(BluetoothStatesTO.class).bluetoothStates; } catch (ConnectionException e) { - logger.debug("failed to get bluetooth state: {}", e.getMessage()); - return new JsonBluetoothStates(); + logger.debug("Failed to get bluetooth state: {}", e.getMessage()); + return List.of(); } } - public @Nullable JsonPlaylists getPlaylists(Device device) throws ConnectionException { - String json = makeRequestAndReturnString( - alexaServer + "/api/cloudplayer/playlists?deviceSerialNumber=" + device.serialNumber + "&deviceType=" - + device.deviceType + "&mediaOwnerCustomerId=" + getCustomerId(device.deviceOwnerCustomerId)); - return parseJson(json, JsonPlaylists.class); - } - - public void command(Device device, String command) throws ConnectionException { - String url = alexaServer + "/api/np/command?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + public void command(DeviceTO device, Object command) throws ConnectionException { + String url = getAlexaServer() + "/api/np/command?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType; - makeRequest("POST", url, command, true, true, Map.of(), 0); - } - - public void smartHomeCommand(String entityId, String action) throws IOException, InterruptedException { - smartHomeCommand(entityId, action, Map.of()); + requestBuilder.post(url).withContent(command).retry(false).syncSend(); } public void smartHomeCommand(String entityId, String action, Map values) throws IOException, InterruptedException { - String url = alexaServer + "/api/phoenix/state"; + String url = getAlexaServer() + "/api/phoenix/state"; JsonObject json = new JsonObject(); JsonArray controlRequests = new JsonArray(); @@ -1019,151 +656,97 @@ public void smartHomeCommand(String entityId, String action, Map controlRequests.add(controlRequest); json.add("controlRequests", controlRequests); - String requestBody = json.toString(); try { - String resultBody = makeRequestAndReturnString("PUT", url, requestBody, true, Map.of()); - logger.trace("Request '{}' resulted in '{}", requestBody, resultBody); - JsonObject result = parseJson(resultBody, JsonObject.class); + JsonObject result = requestBuilder.put(url).withContent(json).syncSend(JsonObject.class); if (result == null) { return; } JsonElement errors = result.get("errors"); if (errors != null && errors.isJsonArray()) { JsonArray errorList = errors.getAsJsonArray(); - if (errorList.size() > 0) { - logger.warn("Smart home device command failed. The request '{}' resulted in error(s): {}", - requestBody, StreamSupport.stream(errorList.spliterator(), false).map(JsonElement::toString) + if (!errorList.isEmpty()) { + logger.warn("Smart home device command failed. {}", + StreamSupport.stream(errorList.spliterator(), false).map(JsonElement::toString) .collect(Collectors.joining(" / "))); } } } catch (ConnectionException e) { - logger.warn("Request URL '{}' failed '{}': {}", url, requestBody, e.getMessage()); + logger.warn("Request to URL '{}' failed: {}", url, e.getMessage()); } } - public void notificationVolume(Device device, int volume) throws ConnectionException { - String url = alexaServer + "/api/device-notification-state/" + device.deviceType + "/" + device.softwareVersion - + "/" + device.serialNumber; - String command = "{\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType - + "\",\"softwareVersion\":\"" + device.softwareVersion + "\",\"volumeLevel\":" + volume + "}"; - makeRequest("PUT", url, command, true, true, Map.of(), 0); + public void setNotificationVolume(DeviceTO device, int volume) throws ConnectionException { + String url = getAlexaServer() + "/api/device-notification-state/" + device.deviceType + "/" + + device.softwareVersion + "/" + device.serialNumber; + NotificationStateTO command = new NotificationStateTO(); + command.deviceSerialNumber = device.serialNumber; + command.deviceType = device.deviceType; + command.deviceSerialNumber = device.softwareVersion; + command.volumeLevel = volume; + requestBuilder.put(url).withContent(command).retry(false).syncSend(); } - public void ascendingAlarm(Device device, boolean ascendingAlarm) throws ConnectionException { - String url = alexaServer + "/api/ascending-alarm/" + device.serialNumber; - String command = "{\"ascendingAlarmEnabled\":" + (ascendingAlarm ? "true" : "false") - + ",\"deviceSerialNumber\":\"" + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType - + "\",\"deviceAccountId\":null}"; - makeRequest("PUT", url, command, true, true, Map.of(), 0); + public void setAscendingAlarm(DeviceTO device, boolean ascendingAlarm) throws ConnectionException { + String url = getAlexaServer() + "/api/ascending-alarm/" + device.serialNumber; + AscendingAlarmModelTO command = new AscendingAlarmModelTO(); + command.ascendingAlarmEnabled = ascendingAlarm; + command.deviceSerialNumber = device.serialNumber; + command.deviceType = device.deviceType; + requestBuilder.put(url).withContent(command).retry(false).syncSend(); } - public void doNotDisturb(Device device, boolean doNotDisturb) throws ConnectionException { - String url = alexaServer + "/api/dnd/status"; - String command = "{\"enabled\":" + (doNotDisturb ? "true" : "false") + ",\"deviceSerialNumber\":\"" - + device.serialNumber + "\",\"deviceType\":\"" + device.deviceType + "\",\"deviceAccountId\":null}"; - makeRequest("PUT", url, command, true, true, Map.of(), 0); + public void setDoNotDisturb(DeviceTO device, boolean doNotDisturb) throws ConnectionException { + String url = getAlexaServer() + "/api/dnd/status"; + DoNotDisturbDeviceStatusTO command = new DoNotDisturbDeviceStatusTO(); + command.enabled = doNotDisturb; + command.deviceSerialNumber = device.serialNumber; + command.deviceType = device.deviceType; + requestBuilder.put(url).withContent(command).retry(false).syncSend(); } - public List getDeviceNotificationStates() { + public List getDeviceNotificationStates() { try { - String json = makeRequestAndReturnString(alexaServer + "/api/device-notification-state"); - JsonDeviceNotificationState result = parseJson(json, JsonDeviceNotificationState.class); - if (result == null) { - return List.of(); - } - return Objects.requireNonNullElse(result.deviceNotificationStates, List.of()); + return requestBuilder.get(getAlexaServer() + "/api/device-notification-state") + .syncSend(DeviceNotificationStatesTO.class).deviceNotificationStates; } catch (ConnectionException e) { logger.info("Error getting device notification states", e); } return List.of(); } - public List getAscendingAlarm() { + public List getAscendingAlarms() { try { - String json = makeRequestAndReturnString(alexaServer + "/api/ascending-alarm"); - JsonAscendingAlarm result = parseJson(json, JsonAscendingAlarm.class); - if (result == null) { - return List.of(); - } - return Objects.requireNonNullElse(result.ascendingAlarmModelList, List.of()); + return requestBuilder.get(getAlexaServer() + "/api/ascending-alarm") + .syncSend(AscendingAlarmModelsTO.class).ascendingAlarmModelList; } catch (ConnectionException e) { logger.info("Error getting ascending alarm states", e); } return List.of(); } - public List getDoNotDisturb() { + public List getDoNotDisturbs() { try { - String json = makeRequestAndReturnString(alexaServer + "/api/dnd/device-status-list"); - JsonDoNotDisturb result = parseJson(json, JsonDoNotDisturb.class); - if (result == null) { - return List.of(); - } - return Objects.requireNonNullElse(result.doNotDisturbDeviceStatusList, List.of()); + return requestBuilder.get(getAlexaServer() + "/api/dnd/device-status-list") + .syncSend(DoNotDisturbDeviceStatusesTO.class).doNotDisturbDeviceStatusList; } catch (ConnectionException e) { logger.info("Error getting do not disturb status list", e); } return List.of(); } - public void bluetooth(Device device, @Nullable String address) throws ConnectionException { + public void bluetooth(DeviceTO device, @Nullable String address) throws ConnectionException { if (address == null || address.isEmpty()) { + String url = getAlexaServer() + "/api/bluetooth/disconnect-sink/" + device.deviceType + "/" + + device.serialNumber; // disconnect - makeRequest("POST", - alexaServer + "/api/bluetooth/disconnect-sink/" + device.deviceType + "/" + device.serialNumber, "", - true, true, Map.of(), 0); - } else { - makeRequest("POST", - alexaServer + "/api/bluetooth/pair-sink/" + device.deviceType + "/" + device.serialNumber, - "{\"bluetoothDeviceAddress\":\"" + address + "\"}", true, true, Map.of(), 0); - } - } - - private @Nullable String getCustomerId(@Nullable String defaultId) { - String accountCustomerId = this.loginData.accountCustomerId; - return accountCustomerId == null || accountCustomerId.isEmpty() ? defaultId : accountCustomerId; - } - - public void playRadio(Device device, @Nullable String stationId) throws ConnectionException { - if (stationId == null || stationId.isEmpty()) { - command(device, "{\"type\":\"PauseCommand\"}"); - } else { - String content = "[\"music/tuneIn/stationId\",\"" + stationId + "\"]|{\"previousPageId\":null}"; - String contentToken = new String( - Base64.getEncoder().encode(Base64.getEncoder().encode(content.getBytes(StandardCharsets.UTF_8)))); - String body = "{\"contentToken\":\"music:" + contentToken + "\"}"; - makeRequest("PUT", alexaServer + "/api/entertainment/v1/player/queue?deviceSerialNumber=" - + device.serialNumber + "&deviceType=" + device.deviceType, body, true, true, Map.of(), 0); - } - } - - public void playAmazonMusicTrack(Device device, @Nullable String trackId) throws ConnectionException { - if (trackId == null || trackId.isEmpty()) { - command(device, "{\"type\":\"PauseCommand\"}"); + requestBuilder.post(url).retry(false).syncSend(); } else { - String command = "{\"trackId\":\"" + trackId + "\",\"playQueuePrime\":true}"; - makeRequest("POST", - alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber - + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" - + getCustomerId(device.deviceOwnerCustomerId) + "&shuffle=false", - command, true, true, Map.of(), 0); + String url = getAlexaServer() + "/api/bluetooth/pair-sink/" + device.deviceType + "/" + device.serialNumber; + requestBuilder.post(url).withContent(Map.of("bluetoothDeviceAddress", address)).retry(false).syncSend(); } } - public void playAmazonMusicPlayList(Device device, @Nullable String playListId) throws ConnectionException { - if (playListId == null || playListId.isEmpty()) { - command(device, "{\"type\":\"PauseCommand\"}"); - } else { - String command = "{\"playlistId\":\"" + playListId + "\",\"playQueuePrime\":true}"; - makeRequest("POST", - alexaServer + "/api/cloudplayer/queue-and-play?deviceSerialNumber=" + device.serialNumber - + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" - + getCustomerId(device.deviceOwnerCustomerId) + "&shuffle=false", - command, true, true, Map.of(), 0); - } - } - - public void announcement(Device device, String speak, String bodyText, @Nullable String title, + public void announcement(DeviceTO device, String speak, String bodyText, @Nullable String title, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { String trimmedSpeak = speak.replaceAll("\\s+", " ").trim(); String trimmedBodyText = bodyText.replaceAll("\\s+", " ").trim(); @@ -1201,21 +784,17 @@ private void sendAnnouncement() { while (iterator.hasNext()) { AnnouncementWrapper announcement = iterator.next(); try { - List devices = announcement.getDevices(); + List devices = announcement.getDevices(); if (!devices.isEmpty()) { - JsonAnnouncementContent content = new JsonAnnouncementContent(announcement); - - Map parameters = new HashMap<>(); - parameters.put("expireAfter", "PT5S"); - parameters.put("content", new JsonAnnouncementContent[] { content }); - parameters.put("target", new JsonAnnouncementTarget(devices)); - - String customerId = getCustomerId(devices.get(0).deviceOwnerCustomerId); - if (customerId != null) { - parameters.put("customerId", customerId); - } - executeSequenceCommandWithVolume(devices, "AlexaAnnouncement", parameters, - announcement.getTtsVolumes(), announcement.getStandardVolumes()); + AnnouncementTO announcementTO = new AnnouncementTO(); + announcementTO.content = List.of(announcement.toAnnouncementTO()); + announcementTO.customerId = loginData.getAccountCustomerId(); + announcementTO.target.customerId = devices.get(0).deviceOwnerCustomerId; + announcementTO.target.devices = devices.stream().map(TOMapper::mapAnnouncementTargetDevice) + .toList(); + executeSequenceCommandWithVolume(devices, "AlexaAnnouncement", + TOMapper.mapToMap(gson, announcementTO), announcement.getTtsVolumes(), + announcement.getStandardVolumes()); } } catch (Exception e) { logger.warn("send announcement fails with unexpected error", e); @@ -1229,7 +808,7 @@ private void sendAnnouncement() { } } - public void textToSpeech(Device device, String text, @Nullable Integer ttsVolume, + public void textToSpeech(DeviceTO device, String text, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { String trimmedText = text.replaceAll("\\s+", " ").trim(); String plainText = trimmedText.replaceAll("<.+?>", "").trim(); @@ -1263,7 +842,7 @@ private void sendTextToSpeech() { while (iterator.hasNext()) { TextWrapper textToSpeech = iterator.next(); try { - List devices = textToSpeech.getDevices(); + List devices = textToSpeech.getDevices(); if (!devices.isEmpty()) { executeSequenceCommandWithVolume(devices, "Alexa.Speak", Map.of("textToSpeak", textToSpeech.getText()), textToSpeech.getTtsVolumes(), @@ -1281,7 +860,8 @@ private void sendTextToSpeech() { } } - public void textCommand(Device device, String text, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { + public void textCommand(DeviceTO device, String text, @Nullable Integer ttsVolume, + @Nullable Integer standardVolume) { String trimmedText = text.replaceAll("\\s+", " ").trim(); String plainText = trimmedText.replaceAll("<.+?>", "").trim(); if (plainText.isEmpty()) { @@ -1315,7 +895,7 @@ private synchronized void sendTextCommand() { while (iterator.hasNext()) { TextWrapper textCommand = iterator.next(); try { - List devices = textCommand.getDevices(); + List devices = textCommand.getDevices(); if (!devices.isEmpty()) { executeSequenceCommandWithVolume(devices, "Alexa.TextCommand", Map.of("text", textCommand.getText()), textCommand.getTtsVolumes(), @@ -1333,7 +913,7 @@ private synchronized void sendTextCommand() { } } - public void volume(Device device, int vol) { + public void setVolume(DeviceTO device, int vol) { // we lock volume until we have finished adding this one Lock lock = Objects.requireNonNull(locks.computeIfAbsent(TimerType.VOLUME, k -> new ReentrantLock())); lock.lock(); @@ -1358,7 +938,7 @@ private void sendVolume() { while (iterator.hasNext()) { Volume volume = iterator.next(); try { - List devices = volume.devices; + List devices = volume.devices; if (!devices.isEmpty()) { executeSequenceCommandWithVolume(devices, null, Map.of(), volume.volumes, List.of()); } @@ -1374,7 +954,7 @@ private void sendVolume() { } } - private void executeSequenceCommandWithVolume(List devices, @Nullable String command, + private void executeSequenceCommandWithVolume(List devices, @Nullable String command, Map parameters, List<@Nullable Integer> ttsVolumes, List<@Nullable Integer> standardVolumes) { JsonArray serialNodesToExecute = new JsonArray(); @@ -1384,11 +964,11 @@ private void executeSequenceCommandWithVolume(List devices, @Nullable St Integer ttsVolume = ttsVolumes.size() > i ? ttsVolumes.get(i) : null; Integer standardVolume = standardVolumes.size() > i ? standardVolumes.get(i) : null; if (ttsVolume != null && !ttsVolume.equals(standardVolume)) { - ttsVolumeNodesToExecute.add( - createExecutionNode(devices.get(i), "Alexa.DeviceControls.Volume", Map.of("value", ttsVolume))); + ttsVolumeNodesToExecute.add(createExecutionNode(devices.get(i).deviceType, devices.get(i).serialNumber, + "Alexa.DeviceControls.Volume", Map.of("value", ttsVolume))); } } - if (ttsVolumeNodesToExecute.size() > 0) { + if (!ttsVolumeNodesToExecute.isEmpty()) { JsonObject parallelNodesToExecute = new JsonObject(); parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode"); parallelNodesToExecute.add("nodesToExecute", ttsVolumeNodesToExecute); @@ -1398,13 +978,15 @@ private void executeSequenceCommandWithVolume(List devices, @Nullable St if (command != null && !parameters.isEmpty()) { JsonArray commandNodesToExecute = new JsonArray(); if ("Alexa.Speak".equals(command) || "Alexa.TextCommand".equals(command)) { - for (Device device : devices) { - commandNodesToExecute.add(createExecutionNode(device, command, parameters)); + for (DeviceTO device : devices) { + commandNodesToExecute + .add(createExecutionNode(device.deviceType, device.serialNumber, command, parameters)); } } else { - commandNodesToExecute.add(createExecutionNode(devices.get(0), command, parameters)); + commandNodesToExecute.add(createExecutionNode(devices.get(0).deviceType, devices.get(0).serialNumber, + command, parameters)); } - if (commandNodesToExecute.size() > 0) { + if (!commandNodesToExecute.isEmpty()) { JsonObject parallelNodesToExecute = new JsonObject(); parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode"); parallelNodesToExecute.add("nodesToExecute", commandNodesToExecute); @@ -1417,22 +999,23 @@ private void executeSequenceCommandWithVolume(List devices, @Nullable St Integer standardVolume = standardVolumes.size() > i ? standardVolumes.get(i) : null; Integer ttsVolume = ttsVolumes.size() > i ? ttsVolumes.get(i) : null; if (ttsVolume != null && standardVolume != null && !standardVolume.equals(ttsVolume)) { - standardVolumeNodesToExecute.add(createExecutionNode(devices.get(i), "Alexa.DeviceControls.Volume", - Map.of("value", standardVolume))); + standardVolumeNodesToExecute.add(createExecutionNode(devices.get(i).deviceType, + devices.get(i).serialNumber, "Alexa.DeviceControls.Volume", Map.of("value", standardVolume))); } } - if (standardVolumeNodesToExecute.size() > 0 && !"AlexaAnnouncement".equals(command)) { + if (!standardVolumeNodesToExecute.isEmpty() && !"AlexaAnnouncement".equals(command)) { JsonObject parallelNodesToExecute = new JsonObject(); parallelNodesToExecute.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode"); parallelNodesToExecute.add("nodesToExecute", standardVolumeNodesToExecute); serialNodesToExecute.add(parallelNodesToExecute); } - if (serialNodesToExecute.size() > 0) { - executeSequenceNodes(devices, serialNodesToExecute, false); + if (!serialNodesToExecute.isEmpty()) { + executeSequenceNodes(devices.stream().map(d -> d.serialNumber).toList(), serialNodesToExecute, false); - if (standardVolumeNodesToExecute.size() > 0 && "AlexaAnnouncement".equals(command)) { - executeSequenceNodes(devices, standardVolumeNodesToExecute, true); + if (!standardVolumeNodesToExecute.isEmpty() && "AlexaAnnouncement".equals(command)) { + executeSequenceNodes(devices.stream().map(d -> d.serialNumber).toList(), standardVolumeNodesToExecute, + true); } } } @@ -1440,25 +1023,24 @@ private void executeSequenceCommandWithVolume(List devices, @Nullable St // commands: Alexa.Weather.Play, Alexa.Traffic.Play, Alexa.FlashBriefing.Play, // Alexa.GoodMorning.Play, // Alexa.SingASong.Play, Alexa.TellStory.Play, Alexa.Speak (textToSpeach) - public void executeSequenceCommand(Device device, String command, Map parameters) { - JsonObject nodeToExecute = createExecutionNode(device, command, parameters); - executeSequenceNode(List.of(device), nodeToExecute); + public void executeSequenceCommand(DeviceTO device, String command, Map parameters) { + JsonObject nodeToExecute = createExecutionNode(device.deviceType, device.serialNumber, command, parameters); + executeSequenceNode(List.of(device.serialNumber), nodeToExecute); } - private void executeSequenceNode(List devices, JsonObject nodeToExecute) { + private void executeSequenceNode(List serialNumbers, JsonObject nodeToExecute) { QueueObject queueObject = new QueueObject(); - queueObject.devices = devices; + queueObject.deviceSerialNumbers = serialNumbers; queueObject.nodeToExecute = nodeToExecute; - String serialNumbers = ""; - for (Device device : devices) { - String serialNumber = device.serialNumber; + List serials = new ArrayList<>(); + for (String serialNumber : serialNumbers) { if (serialNumber != null) { Objects.requireNonNull(this.devices.computeIfAbsent(serialNumber, k -> new LinkedBlockingQueue<>())) .offer(queueObject); - serialNumbers = serialNumbers + device.serialNumber + " "; + serials.add(serialNumber); } } - logger.debug("added {} device {}", queueObject.hashCode(), serialNumbers); + logger.debug("Added {} device(s) {} to queue", queueObject.hashCode(), serials); } private void handleExecuteSequenceNode() { @@ -1473,11 +1055,10 @@ private void handleExecuteSequenceNode() { Future future = queueObject.future; if (future == null || future.isDone()) { boolean execute = true; - String serial = ""; - for (Device tmpDevice : queueObject.devices) { - if (!serialNumber.equals(tmpDevice.serialNumber)) { - LinkedBlockingQueue tmpQueueObjects = devices - .get(tmpDevice.serialNumber); + List serials = new ArrayList<>(); + for (String tmpDevice : queueObject.deviceSerialNumbers) { + if (Objects.equals(tmpDevice, serialNumber)) { + LinkedBlockingQueue tmpQueueObjects = devices.get(tmpDevice); if (tmpQueueObjects != null) { QueueObject tmpQueueObject = tmpQueueObjects.peek(); Future tmpFuture = null; @@ -1490,13 +1071,13 @@ private void handleExecuteSequenceNode() { break; } - serial = serial + tmpDevice.serialNumber + " "; + serials.add(tmpDevice); } } } if (execute) { queueObject.future = scheduler.submit(() -> queuedExecuteSequenceNode(queueObject)); - logger.debug("thread {} device {}", queueObject.hashCode(), serial); + logger.debug("thread {} device(s) {}", queueObject.hashCode(), serials); } } } @@ -1511,11 +1092,6 @@ private void handleExecuteSequenceNode() { private void queuedExecuteSequenceNode(QueueObject queueObject) { JsonObject nodeToExecute = queueObject.nodeToExecute; ExecutionNodeObject executionNodeObject = getExecutionNodeObject(nodeToExecute); - if (executionNodeObject == null) { - logger.debug("executionNodeObject empty, removing without execution"); - removeObjectFromQueueAfterExecutionCompletion(queueObject); - return; - } List types = executionNodeObject.types; long delay = types.contains("Alexa.DeviceControls.Volume") ? 2000 : 0; delay += types.contains("Announcement") ? 3000 : 2000; @@ -1525,17 +1101,15 @@ private void queuedExecuteSequenceNode(QueueObject queueObject) { sequenceJson.addProperty("@type", "com.amazon.alexa.behaviors.model.Sequence"); sequenceJson.add("startNode", nodeToExecute); - JsonStartRoutineRequest request = new JsonStartRoutineRequest(); + StartRoutineTO request = new StartRoutineTO(); request.sequenceJson = gson.toJson(sequenceJson); - String json = gson.toJson(request); String text = executionNodeObject.text; if (text != null) { text = text.replaceAll("<.+?>", " ").replaceAll("\\s+", " ").trim(); - delay += text.length() * 150; + delay += text.length() * 150L; } - - makeRequest("POST", alexaServer + "/api/behaviors/preview", json, true, true, Map.of(), 3); + requestBuilder.post(getAlexaServer() + "/api/behaviors/preview").withContent(request).syncSend(); Thread.sleep(delay); } catch (ConnectionException | InterruptedException e) { @@ -1546,21 +1120,18 @@ private void queuedExecuteSequenceNode(QueueObject queueObject) { } private void removeObjectFromQueueAfterExecutionCompletion(QueueObject queueObject) { - String serial = ""; - for (Device device : queueObject.devices) { - String serialNumber = device.serialNumber; - if (serialNumber != null) { - LinkedBlockingQueue queue = devices.get(serialNumber); - if (queue != null) { - queue.remove(queueObject); - } - serial = serial + serialNumber + " "; + List serials = new ArrayList<>(); + for (String serialNumber : queueObject.deviceSerialNumbers) { + LinkedBlockingQueue queue = devices.get(serialNumber); + if (queue != null) { + queue.remove(queueObject); } + serials.add(serialNumber); } - logger.debug("removed {} device {}", queueObject.hashCode(), serial); + logger.debug("Removed {} device(s) {} from queue", queueObject.hashCode(), serials); } - private void executeSequenceNodes(List devices, JsonArray nodesToExecute, boolean parallel) { + private void executeSequenceNodes(List serialNumbers, JsonArray nodesToExecute, boolean parallel) { JsonObject serialNode = new JsonObject(); if (parallel) { serialNode.addProperty("@type", "com.amazon.alexa.behaviors.model.ParallelNode"); @@ -1570,17 +1141,16 @@ private void executeSequenceNodes(List devices, JsonArray nodesToExecute serialNode.add("nodesToExecute", nodesToExecute); - executeSequenceNode(devices, serialNode); + executeSequenceNode(serialNumbers, serialNode); } - private JsonObject createExecutionNode(@Nullable Device device, String command, Map parameters) { + private JsonObject createExecutionNode(String deviceType, String serialNumber, String command, + Map parameters) { JsonObject operationPayload = new JsonObject(); - if (device != null) { - operationPayload.addProperty("deviceType", device.deviceType); - operationPayload.addProperty("deviceSerialNumber", device.serialNumber); - operationPayload.addProperty("locale", ""); - operationPayload.addProperty("customerId", getCustomerId(device.deviceOwnerCustomerId)); - } + operationPayload.addProperty("deviceType", deviceType); + operationPayload.addProperty("deviceSerialNumber", serialNumber); + operationPayload.addProperty("locale", ""); + operationPayload.addProperty("customerId", loginData.getAccountCustomerId()); for (String key : parameters.keySet()) { Object value = parameters.get(key); if (value instanceof String) { @@ -1606,18 +1176,17 @@ private JsonObject createExecutionNode(@Nullable Device device, String command, return nodeToExecute; } - @Nullable private ExecutionNodeObject getExecutionNodeObject(JsonObject nodeToExecute) { ExecutionNodeObject executionNodeObject = new ExecutionNodeObject(); if (nodeToExecute.has("nodesToExecute")) { JsonArray serialNodesToExecute = nodeToExecute.getAsJsonArray("nodesToExecute"); - if (serialNodesToExecute != null && serialNodesToExecute.size() > 0) { + if (serialNodesToExecute != null && !serialNodesToExecute.isEmpty()) { for (int i = 0; i < serialNodesToExecute.size(); i++) { JsonObject serialNodesToExecuteJsonObject = serialNodesToExecute.get(i).getAsJsonObject(); if (serialNodesToExecuteJsonObject.has("nodesToExecute")) { JsonArray parallelNodesToExecute = serialNodesToExecuteJsonObject .getAsJsonArray("nodesToExecute"); - if (parallelNodesToExecute != null && parallelNodesToExecute.size() > 0) { + if (parallelNodesToExecute != null && !parallelNodesToExecute.isEmpty()) { JsonObject parallelNodesToExecuteJsonObject = parallelNodesToExecute.get(0) .getAsJsonObject(); if (processNodesToExecuteJsonObject(executionNodeObject, @@ -1652,7 +1221,7 @@ private boolean processNodesToExecuteJsonObject(ExecutionNodeObject executionNod return true; } else if (operationPayload.has("content")) { JsonArray content = operationPayload.getAsJsonArray("content"); - if (content != null && content.size() > 0) { + if (content != null && !content.isEmpty()) { JsonObject contentJsonObject = content.get(0).getAsJsonObject(); if (contentJsonObject.has("speak")) { JsonObject speak = contentJsonObject.getAsJsonObject("speak"); @@ -1669,21 +1238,20 @@ private boolean processNodesToExecuteJsonObject(ExecutionNodeObject executionNod return false; } - public void startRoutine(Device device, String utterance) throws ConnectionException { - JsonAutomation found = null; + public void startRoutine(DeviceTO device, String utterance) throws ConnectionException { + AutomationTO found = null; String deviceLocale = ""; - List routines = getRoutines(); + List routines = getAutomations(); - for (JsonAutomation routine : routines) { + for (AutomationTO routine : routines) { if (routine.sequence != null) { - List triggers = Objects.requireNonNullElse(routine.triggers, List.of()); - for (JsonAutomation.Trigger trigger : triggers) { - Payload payload = trigger.payload; + for (AutomationTriggerTO trigger : routine.triggers) { + AutomationPayloadTO payload = trigger.payload; if (payload == null) { continue; } String payloadUtterance = payload.utterance; - if (payloadUtterance != null && payloadUtterance.equalsIgnoreCase(utterance)) { + if (utterance.equalsIgnoreCase(payloadUtterance)) { found = routine; deviceLocale = payload.locale; break; @@ -1694,7 +1262,7 @@ public void startRoutine(Device device, String utterance) throws ConnectionExcep if (found != null) { String sequenceJson = gson.toJson(found.sequence); - JsonStartRoutineRequest request = new JsonStartRoutineRequest(); + StartRoutineTO request = new StartRoutineTO(); request.behaviorId = found.automationId; // replace tokens @@ -1710,7 +1278,7 @@ public void startRoutine(Device device, String utterance) throws ConnectionExcep // "customerId": "ALEXA_CUSTOMER_ID" String customerId = "\"customerId\":\"ALEXA_CUSTOMER_ID\""; - String newCustomerId = "\"customerId\":\"" + getCustomerId(device.deviceOwnerCustomerId) + "\""; + String newCustomerId = "\"customerId\":\"" + loginData.getAccountCustomerId() + "\""; sequenceJson = sequenceJson.replace(customerId, newCustomerId); // "locale": "ALEXA_CURRENT_LOCALE" @@ -1721,185 +1289,172 @@ public void startRoutine(Device device, String utterance) throws ConnectionExcep request.sequenceJson = sequenceJson; - String requestJson = gson.toJson(request); - makeRequest("POST", alexaServer + "/api/behaviors/preview", requestJson, true, true, Map.of(), 3); + requestBuilder.post(getAlexaServer() + "/api/behaviors/preview").withContent(request).syncSend(); } else { logger.warn("Routine {} not found", utterance); } } - public List getRoutines() throws ConnectionException { - String json = makeRequestAndReturnString(alexaServer + "/api/behaviors/v2/automations?limit=2000"); - JsonAutomation[] result = parseJson(json, JsonAutomation[].class); - if (result == null) { - return List.of(); - } - return Arrays.asList(result); + public List getAutomations() throws ConnectionException { + return requestBuilder.get(getAlexaServer() + "/api/behaviors/v2/automations?limit=2000") + .syncSend(AutomationTO.LIST_TYPE_TOKEN); } - public List getEnabledFlashBriefings() throws ConnectionException { - String json = makeRequestAndReturnString(alexaServer + "/api/content-skills/enabled-feeds"); - JsonEnabledFeeds result = parseJson(json, JsonEnabledFeeds.class); - if (result == null) { - return List.of(); + public List getEnabledFlashBriefings() { + try { + return requestBuilder.get(getAlexaServer() + "/api/content-skills/enabled-feeds") + .syncSend(EnabledFeedsTO.class).enabledFeeds; + } catch (ConnectionException e) { + logger.warn("Failed to get enabled feeds: {}", e.getMessage()); } - return Objects.requireNonNullElse(result.enabledFeeds, List.of()); + return List.of(); } - public void setEnabledFlashBriefings(List enabledFlashBriefing) throws ConnectionException { - JsonEnabledFeeds enabled = new JsonEnabledFeeds(); + public void setEnabledFlashBriefings(List enabledFlashBriefing) throws ConnectionException { + EnabledFeedsTO enabled = new EnabledFeedsTO(); enabled.enabledFeeds = enabledFlashBriefing; - String json = gsonWithNullSerialization.toJson(enabled); - makeRequest("POST", alexaServer + "/api/content-skills/enabled-feeds", json, true, true, Map.of(), 0); + requestBuilder.post(getAlexaServer() + "/api/content-skills/enabled-feeds").withContent(enabled).retry(false) + .syncSend(); } - public List getNotificationSounds(Device device) throws ConnectionException { - String json = makeRequestAndReturnString( - alexaServer + "/api/notification/sounds?deviceSerialNumber=" + device.serialNumber + "&deviceType=" - + device.deviceType + "&softwareVersion=" + device.softwareVersion); - JsonNotificationSounds result = parseJson(json, JsonNotificationSounds.class); - if (result == null) { + public List getNotificationSounds(DeviceTO device) { + try { + return requestBuilder + .get(getAlexaServer() + "/api/notification/sounds?deviceSerialNumber=" + device.serialNumber + + "&deviceType=" + device.deviceType + "&softwareVersion=" + device.softwareVersion) + .syncSend(NotificationSoundResponseTO.class).notificationSounds; + } catch (ConnectionException e) { return List.of(); } - return Objects.requireNonNullElse(result.notificationSounds, List.of()); } - public List notifications() throws ConnectionException { - String response = makeRequestAndReturnString(alexaServer + "/api/notifications"); - JsonNotificationsResponse result = parseJson(response, JsonNotificationsResponse.class); - if (result == null) { - return List.of(); + public List getNotifications() { + try { + return requestBuilder.get(getAlexaServer() + "/api/notifications") + .syncSend(NotificationListResponseTO.class).notifications; + } catch (ConnectionException e) { + logger.warn("Failed to get notifications: {}", e.getMessage()); } - return Objects.requireNonNullElse(result.notifications, List.of()); + return List.of(); + } + + public NotificationTO getNotification(String notificationId) throws ConnectionException { + String url = getAlexaServer() + "/api/notifications/" + notificationId; + return requestBuilder.get(url).syncSend(NotificationTO.class); } - public @Nullable JsonNotificationResponse notification(Device device, String type, @Nullable String label, - @Nullable JsonNotificationSound sound) throws ConnectionException { - Date date = new Date(new Date().getTime()); - long createdDate = date.getTime(); - Date alarm = new Date(createdDate + 5000); // add 5 seconds, because amazon does not accept calls for times in - // the past (compared with the server time) - long alarmTime = alarm.getTime(); + public @Nullable NotificationTO createNotification(DeviceTO device, String type, @Nullable String label, + @Nullable NotificationSoundTO sound) throws ConnectionException { + Instant createdTime = Instant.now(); + // add 5 seconds, because amazon does not accept calls for times in the past (compared with the server + // nextAlarmTime) + Instant alarmTime = createdTime.plusSeconds(5); + ZonedDateTime zonedAlarmTime = alarmTime.atZone(ZoneId.systemDefault()); - JsonNotificationRequest request = new JsonNotificationRequest(); - request.type = type; + NotificationTO request = new NotificationTO(); + request.status = "ON"; request.deviceSerialNumber = device.serialNumber; request.deviceType = device.deviceType; - request.createdDate = createdDate; - request.alarmTime = alarmTime; + request.createdDate = createdTime.getEpochSecond() * 1000; + request.alarmTime = alarmTime.getEpochSecond() * 1000; request.reminderLabel = label; request.sound = sound; - request.originalDate = new SimpleDateFormat("yyyy-MM-dd").format(alarm); - request.originalTime = new SimpleDateFormat("HH:mm:ss.SSSS").format(alarm); + request.originalDate = zonedAlarmTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + request.originalTime = zonedAlarmTime.format(DateTimeFormatter.ofPattern("HH:mm:ss.SSSS")); request.type = type; request.id = "create" + type; + request.isSaveInFlight = true; + request.isRecurring = false; - String data = gsonWithNullSerialization.toJson(request); - String response = makeRequestAndReturnString("PUT", alexaServer + "/api/notifications/createReminder", data, - true, Map.of()); - JsonNotificationResponse result = parseJson(response, JsonNotificationResponse.class); - return result; - } - - public void stopNotification(JsonNotificationResponse notification) throws ConnectionException { - makeRequestAndReturnString("DELETE", alexaServer + "/api/notifications/" + notification.id, null, true, - Map.of()); + String url = getAlexaServer() + "/api/notifications/createReminder"; + return requestBuilder.put(url).withContent(request).syncSend(NotificationTO.class); } - public @Nullable JsonNotificationResponse getNotificationState(JsonNotificationResponse notification) - throws ConnectionException { - String response = makeRequestAndReturnString(alexaServer + "/api/notifications/" + notification.id); - JsonNotificationResponse result = parseJson(response, JsonNotificationResponse.class); - return result; + public void deleteNotification(String notificationId) { + try { + requestBuilder.delete(getAlexaServer() + "/api/notifications/" + notificationId).syncSend(); + } catch (ConnectionException e) { + logger.warn("Failed to delete notification {}: {}", notificationId, e.getMessage()); + } } - public List getMusicProviders() { + public List getMusicProviders() { try { - Map headers = new HashMap<>(); - headers.put("Routines-Version", "1.1.218665"); - String response = makeRequestAndReturnString("GET", - alexaServer + "/api/behaviors/entities?skillId=amzn1.ask.1p.music", null, true, headers); - if (!response.isEmpty()) { - JsonMusicProvider[] musicProviders = parseJson(response, JsonMusicProvider[].class); - return Arrays.asList(musicProviders); - } + return requestBuilder.get(getAlexaServer() + "/api/behaviors/entities?skillId=amzn1.ask.1p.music") + .withHeader("Routines-Version", "1.1.218665").syncSend(MusicProviderTO.LIST_TYPE_TOKEN); } catch (ConnectionException e) { - logger.warn("getMusicProviders fails: {}", e.getMessage()); + logger.warn("Failed to get music providers: {}", e.getMessage()); } return List.of(); } - public void playMusicVoiceCommand(Device device, String providerId, String voiceCommand) + public void playMusicVoiceCommand(DeviceTO device, String providerId, String voiceCommand) throws ConnectionException { - JsonPlaySearchPhraseOperationPayload payload = new JsonPlaySearchPhraseOperationPayload(); - payload.customerId = getCustomerId(device.deviceOwnerCustomerId); - payload.locale = "ALEXA_CURRENT_LOCALE"; - payload.musicProviderId = providerId; - payload.searchPhrase = voiceCommand; - - String playloadString = gson.toJson(payload); - - JsonObject postValidationJson = new JsonObject(); + PlaySearchPhraseTO playSearchPhrase = new PlaySearchPhraseTO(); + playSearchPhrase.customerId = loginData.getAccountCustomerId(); + playSearchPhrase.musicProviderId = providerId; + playSearchPhrase.searchPhrase = voiceCommand; - postValidationJson.addProperty("type", "Alexa.Music.PlaySearchPhrase"); - postValidationJson.addProperty("operationPayload", playloadString); + BehaviorOperationValidateTO validationRequest = new BehaviorOperationValidateTO(); + validationRequest.type = "Alexa.Music.PlaySearchPhrase"; + validationRequest.operationPayload = gson.toJson(playSearchPhrase); - String postDataValidate = postValidationJson.toString(); + PlaySearchPhraseTO validatedOperationPayload = requestBuilder + .post(getAlexaServer() + "/api/behaviors/operation/validate").withContent(validationRequest) + .syncSend(PlaySearchPhraseTO.VALIDATION_RESULT_TO_TYPE_TOKEN).operationPayload; - String validateResultJson = makeRequestAndReturnString("POST", - alexaServer + "/api/behaviors/operation/validate", postDataValidate, true, Map.of()); - - if (!validateResultJson.isEmpty()) { - JsonPlayValidationResult validationResult = parseJson(validateResultJson, JsonPlayValidationResult.class); - if (validationResult != null) { - JsonPlaySearchPhraseOperationPayload validatedOperationPayload = validationResult.operationPayload; - if (validatedOperationPayload != null) { - payload.sanitizedSearchPhrase = validatedOperationPayload.sanitizedSearchPhrase; - payload.searchPhrase = validatedOperationPayload.searchPhrase; - } - } + if (validatedOperationPayload != null) { + playSearchPhrase.sanitizedSearchPhrase = validatedOperationPayload.sanitizedSearchPhrase; + playSearchPhrase.searchPhrase = validatedOperationPayload.searchPhrase; } - payload.locale = null; - payload.deviceSerialNumber = device.serialNumber; - payload.deviceType = device.deviceType; + playSearchPhrase.locale = null; + playSearchPhrase.deviceSerialNumber = device.serialNumber; + playSearchPhrase.deviceType = device.deviceType; JsonObject sequenceJson = new JsonObject(); sequenceJson.addProperty("@type", "com.amazon.alexa.behaviors.model.Sequence"); JsonObject startNodeJson = new JsonObject(); startNodeJson.addProperty("@type", "com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode"); startNodeJson.addProperty("type", "Alexa.Music.PlaySearchPhrase"); - startNodeJson.add("operationPayload", gson.toJsonTree(payload)); + startNodeJson.add("operationPayload", gson.toJsonTree(playSearchPhrase)); sequenceJson.add("startNode", startNodeJson); - JsonStartRoutineRequest startRoutineRequest = new JsonStartRoutineRequest(); + StartRoutineTO startRoutineRequest = new StartRoutineTO(); startRoutineRequest.sequenceJson = sequenceJson.toString(); startRoutineRequest.status = null; - String postData = gson.toJson(startRoutineRequest); - makeRequest("POST", alexaServer + "/api/behaviors/preview", postData, true, true, Map.of(), 3); + requestBuilder.post(getAlexaServer() + "/api/behaviors/preview").withContent(startRoutineRequest).syncSend(); } - public @Nullable JsonEqualizer getEqualizer(Device device) throws ConnectionException { - String json = makeRequestAndReturnString( - alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType); - return parseJson(json, JsonEqualizer.class); + public Optional getEqualizer(DeviceTO device) { + try { + return Optional.of(requestBuilder + .get(getAlexaServer() + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType) + .syncSend(EqualizerTO.class)); + } catch (ConnectionException e) { + return Optional.empty(); + } } - public void setEqualizer(Device device, JsonEqualizer settings) throws ConnectionException { - String postData = gson.toJson(settings); - makeRequest("POST", alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType, postData, - true, true, Map.of(), 0); + public boolean setEqualizer(DeviceTO device, EqualizerTO settings) { + try { + requestBuilder.post(getAlexaServer() + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType) + .withContent(settings).retry(false).syncSend(); + return true; + } catch (ConnectionException e) { + return false; + } } private static class Volume { - public List devices = new ArrayList<>(); + public List devices = new ArrayList<>(); public List<@Nullable Integer> volumes = new ArrayList<>(); } private static class QueueObject { public @Nullable Future future; - public List devices = List.of(); + public List deviceSerialNumbers = List.of(); public JsonObject nodeToExecute = new JsonObject(); } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/LoginData.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/LoginData.java index 11d12c2042..8007368888 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/LoginData.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/LoginData.java @@ -22,7 +22,6 @@ import java.util.Objects; import java.util.Random; import java.util.Scanner; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -41,14 +40,17 @@ public class LoginData { private final CookieManager cookieManager; // data fields - public String frc; - public String serial; - public String deviceId; - public @Nullable String refreshToken; - public String amazonSite = "amazon.com"; - public String deviceName = "Unknown"; - public @Nullable String accountCustomerId; - public @Nullable Date loginTime; + private String frc; + private String serial; + private String deviceId; + private @Nullable String refreshToken; + private String retailDomain = "amazon.com"; + private String retailUrl = "https://www.amazon.com"; + private String websiteApiUrl = "https://alexa.amazon.com"; + + private String deviceName = "Unknown"; + private String accountCustomerId = ""; + private @Nullable Date loginTime; private List cookies = new ArrayList<>(); public LoginData(CookieManager cookieManager, String deviceId, String frc, String serial) { @@ -78,23 +80,104 @@ public LoginData(CookieManager cookieManager) { this.deviceId = HexUtils.bytesToHex(hexStr.getBytes()); } + public String getFrc() { + return frc; + } + + public String getSerial() { + return serial; + } + + public String getDeviceId() { + return deviceId; + } + + public @Nullable String getRefreshToken() { + return refreshToken; + } + + public String getRetailDomain() { + return retailDomain; + } + + public String getRetailUrl() { + return retailUrl; + } + + public String getWebsiteApiUrl() { + return websiteApiUrl; + } + + public String getDeviceName() { + return deviceName; + } + + public String getAccountCustomerId() { + return accountCustomerId; + } + + public @Nullable Date getLoginTime() { + return loginTime; + } + + public void setFrc(String frc) { + this.frc = frc; + } + + public void setSerial(String serial) { + this.serial = serial; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public void setRefreshToken(@Nullable String refreshToken) { + this.refreshToken = refreshToken; + } + + public void setRetailDomain(String retailDomain) { + this.retailDomain = retailDomain; + } + + public void setRetailUrl(String retailUrl) { + this.retailUrl = retailUrl; + } + + public void setWebsiteApiUrl(String websiteApiUrl) { + this.websiteApiUrl = websiteApiUrl; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public void setAccountCustomerId(String accountCustomerId) { + this.accountCustomerId = accountCustomerId; + } + + public void setLoginTime(@Nullable Date loginTime) { + this.loginTime = loginTime; + } + public String serializeLoginData() { Date loginTime = this.loginTime; if (refreshToken == null || loginTime == null) { return ""; } StringBuilder builder = new StringBuilder(); - builder.append("7\n"); // version + builder.append("8\n"); // version builder.append(frc).append("\n"); builder.append(serial).append("\n"); builder.append(deviceId).append("\n"); builder.append(refreshToken).append("\n"); - builder.append(amazonSite).append("\n"); + builder.append(retailDomain).append("\n"); + builder.append(retailUrl).append("\n"); + builder.append(websiteApiUrl).append("\n"); builder.append(deviceName).append("\n"); builder.append(accountCustomerId).append("\n"); builder.append(loginTime.getTime()).append("\n"); - cookies = cookieManager.getCookieStore().getCookies().stream().map(LoginData.Cookie::fromHttpCookie) - .collect(Collectors.toList()); + cookies = cookieManager.getCookieStore().getCookies().stream().map(LoginData.Cookie::fromHttpCookie).toList(); builder.append(cookies.size()).append("\n"); cookies.forEach(cookie -> builder.append(cookie.serialize())); return builder.toString(); @@ -104,7 +187,7 @@ public boolean deserialize(String data) { Scanner scanner = new Scanner(data); String version = scanner.nextLine(); // check if serialize version is supported - if (!"7".equals(version)) { + if (!"7".equals(version) && !"8".equals(version)) { scanner.close(); return false; } @@ -114,12 +197,21 @@ public boolean deserialize(String data) { deviceId = scanner.nextLine(); refreshToken = scanner.nextLine(); - amazonSite = scanner.nextLine(); + retailDomain = scanner.nextLine(); + if ("8".equals(version)) { + retailUrl = scanner.nextLine(); + websiteApiUrl = scanner.nextLine(); + } else { + // this maybe incorrect, but it's the same code that we used before + retailUrl = "https://www." + retailDomain; + websiteApiUrl = "https://alexa." + retailDomain; + } deviceName = scanner.nextLine(); accountCustomerId = scanner.nextLine(); loginTime = new Date(Long.parseLong(scanner.nextLine())); int numberOfCookies = Integer.parseInt(scanner.nextLine()); + cookies = new ArrayList<>(); for (int i = 0; i < numberOfCookies; i++) { cookies.add(Cookie.fromScanner(scanner)); } @@ -219,4 +311,27 @@ public HttpCookie toHttpCookie() { return clientCookie; } } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + LoginData loginData = (LoginData) o; + return Objects.equals(frc, loginData.frc) && Objects.equals(serial, loginData.serial) + && Objects.equals(deviceId, loginData.deviceId) && Objects.equals(refreshToken, loginData.refreshToken) + && Objects.equals(retailDomain, loginData.retailDomain) + && Objects.equals(retailUrl, loginData.retailUrl) + && Objects.equals(websiteApiUrl, loginData.websiteApiUrl) + && Objects.equals(deviceName, loginData.deviceName) + && Objects.equals(accountCustomerId, loginData.accountCustomerId) + && Objects.equals(loginTime, loginData.loginTime) && Objects.equals(cookies, loginData.cookies); + } + + @Override + public int hashCode() { + return Objects.hash(frc, serial, deviceId, refreshToken, retailDomain, retailUrl, websiteApiUrl, deviceName, + accountCustomerId, loginTime, cookies); + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/TextWrapper.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/TextWrapper.java index 58b2b322a2..8149b4a890 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/TextWrapper.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/connection/TextWrapper.java @@ -17,7 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; /** * The {@link TextWrapper} is a wrapper class for text or TTS instructions @@ -26,7 +26,7 @@ */ @NonNullByDefault public class TextWrapper { - private final List devices = new ArrayList<>(); + private final List devices = new ArrayList<>(); private final String text; private final List<@Nullable Integer> ttsVolumes = new ArrayList<>(); private final List<@Nullable Integer> standardVolumes = new ArrayList<>(); @@ -35,13 +35,13 @@ public TextWrapper(String text) { this.text = text; } - public void add(JsonDevices.Device device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { + public void add(DeviceTO device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) { devices.add(device); ttsVolumes.add(ttsVolume); standardVolumes.add(standardVolume); } - public List getDevices() { + public List getDevices() { return devices; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java index e132fb74c2..4890ad8f67 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java @@ -38,8 +38,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EnabledFeedTO; import org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; /** * The {@link AmazonEchoDiscovery} is responsible for discovering echo devices on @@ -50,15 +51,16 @@ */ @NonNullByDefault public class AmazonEchoDiscovery extends AbstractDiscoveryService implements ThingHandlerService { + private static final int BACKGROUND_INTERVAL = 10; // in seconds private @Nullable AccountHandler accountHandler; private final Logger logger = LoggerFactory.getLogger(AmazonEchoDiscovery.class); - private final Set discoveredFlashBriefings = new HashSet<>(); + private final Set> discoveredFlashBriefings = new HashSet<>(); private @Nullable ScheduledFuture startScanStateJob; private @Nullable Long activateTimeStamp; public AmazonEchoDiscovery() { - super(SUPPORTED_ECHO_THING_TYPES_UIDS, 10); + super(SUPPORTED_ECHO_THING_TYPES_UIDS, 5); } @Override @@ -93,7 +95,7 @@ protected void startScan() { } setDevices(accountHandler.updateDeviceList()); - String currentFlashBriefingConfiguration = accountHandler.getNewCurrentFlashbriefingConfiguration(); + List currentFlashBriefingConfiguration = accountHandler.updateFlashBriefingHandlers(); discoverFlashBriefingProfiles(currentFlashBriefingConfiguration); } @@ -106,25 +108,21 @@ protected void startAutomaticScan() { stopScanJob(); return; } - Connection connection = accountHandler.findConnection(); - if (connection == null) { - return; - } - Date verifyTime = connection.tryGetVerifyTime(); - if (verifyTime == null) { - return; - } - if (new Date().getTime() - verifyTime.getTime() < 10000) { + Connection connection = accountHandler.getConnection(); + // do discovery only if logged in and last login is more than 10 s ago + Date verifyTime = connection.getVerifyTime(); + if (verifyTime == null || System.currentTimeMillis() < (verifyTime.getTime() + 10000)) { return; } + startScan(); } @Override protected void startBackgroundDiscovery() { stopScanJob(); - startScanStateJob = scheduler.scheduleWithFixedDelay(this::startAutomaticScan, 3000, 1000, - TimeUnit.MILLISECONDS); + startScanStateJob = scheduler.scheduleWithFixedDelay(this::startAutomaticScan, BACKGROUND_INTERVAL, + BACKGROUND_INTERVAL, TimeUnit.SECONDS); } @Override @@ -153,12 +151,12 @@ public void activate(@Nullable Map config) { } } - private synchronized void setDevices(List deviceList) { + private synchronized void setDevices(List deviceList) { AccountHandler accountHandler = this.accountHandler; if (accountHandler == null) { return; } - for (Device device : deviceList) { + for (DeviceTO device : deviceList) { String serialNumber = device.serialNumber; if (serialNumber != null) { String deviceFamily = device.deviceFamily; @@ -202,26 +200,22 @@ private synchronized void setDevices(List deviceList) { } } - private synchronized void discoverFlashBriefingProfiles(String currentFlashBriefingJson) { + private synchronized void discoverFlashBriefingProfiles(List enabledFeeds) { AccountHandler accountHandler = this.accountHandler; - if (accountHandler == null) { - return; - } - - if (currentFlashBriefingJson.isEmpty()) { + if (accountHandler == null || enabledFeeds.isEmpty()) { return; } - if (!discoveredFlashBriefings.contains(currentFlashBriefingJson)) { + if (!discoveredFlashBriefings.contains(enabledFeeds)) { ThingUID bridgeThingUID = accountHandler.getThing().getUID(); ThingUID freeThingUID = new ThingUID(THING_TYPE_FLASH_BRIEFING_PROFILE, bridgeThingUID, - Integer.toString(currentFlashBriefingJson.hashCode())); + Integer.toString(enabledFeeds.hashCode())); DiscoveryResult result = DiscoveryResultBuilder.create(freeThingUID).withLabel("FlashBriefing") - .withProperty(DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE, currentFlashBriefingJson) + .withProperty(DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE, enabledFeeds) .withBridge(accountHandler.getThing().getUID()).build(); - logger.debug("Flash Briefing {} discovered", currentFlashBriefingJson); + logger.debug("Flash Briefing {} discovered", enabledFeeds); thingDiscovered(result); - discoveredFlashBriefings.add(currentFlashBriefingJson); + discoveredFlashBriefings.add(enabledFeeds); } } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java index cbc5a69ed3..1c9f862179 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java @@ -33,13 +33,13 @@ import org.openhab.core.thing.binding.ThingHandlerService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice.DriverIdentity; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDeviceAlias; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroups.SmartHomeGroup; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice.DriverIdentity; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDeviceAlias; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; import org.smarthomej.binding.amazonechocontrol.internal.smarthome.Constants; /** @@ -95,7 +95,7 @@ protected void stopScan() { protected void startBackgroundDiscovery() { ScheduledFuture discoveryJob = this.discoveryJob; if (discoveryJob == null || discoveryJob.isCancelled()) { - this.discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 3, 60, TimeUnit.SECONDS); + this.discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 1, 5, TimeUnit.MINUTES); } } @@ -124,9 +124,8 @@ private synchronized void setSmartHomeDevices(List deviceLi String deviceName = null; Map props = new HashMap<>(); - if (smartHomeDevice instanceof JsonSmartHomeDevice) { - JsonSmartHomeDevice shd = (JsonSmartHomeDevice) smartHomeDevice; - logger.trace("Found SmartHome device: {}", shd); + if (smartHomeDevice instanceof JsonSmartHomeDevice shd) { + logger.trace("Found SmartHome device: {}", shd.applianceId); String entityId = shd.entityId; if (entityId == null) { @@ -166,7 +165,7 @@ private synchronized void setSmartHomeDevices(List deviceLi } if (manufacturerName != null && manufacturerName.startsWith("Amazon")) { List<@Nullable String> interfaces = shd.getCapabilities().stream().map(c -> c.interfaceName) - .collect(Collectors.toList()); + .toList(); if (driverIdentity != null && "SonarCloudService".equals(driverIdentity.identifier)) { if (interfaces.contains("Alexa.AcousticEventSensor")) { deviceName = "Alexa Guard on " + shd.friendlyName; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/AscendingAlarmModelTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/AscendingAlarmModelTO.java new file mode 100644 index 0000000000..de85cbdb5e --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/AscendingAlarmModelTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNull; + +/** + * The {@link AscendingAlarmModelTO} encapsulates the ascending alarm status of a device + * + * @author Jan N. Klug - Initial contribution + */ +public class AscendingAlarmModelTO { + public boolean ascendingAlarmEnabled; + public String deviceSerialNumber; + public String deviceType; + @SerializeNull + public Object deviceAccountId = null; + + @Override + public @NonNull String toString() { + return "AscendingAlarmModelTO{ascendingAlarmEnabled=" + ascendingAlarmEnabled + ", deviceSerialNumber='" + + deviceSerialNumber + "', deviceType='" + deviceType + "', deviceAccountId=" + deviceAccountId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPushCommand.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BehaviorOperationValidationResultTO.java similarity index 58% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPushCommand.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BehaviorOperationValidationResultTO.java index ff80d871f7..114993fd14 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPushCommand.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BehaviorOperationValidationResultTO.java @@ -11,18 +11,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +package org.smarthomej.binding.amazonechocontrol.internal.dto; /** - * The {@link JsonPushCommand} encapsulate the GSON data of automation query + * The {@link BehaviorOperationValidationResultTO} encapsulate the GSON for validation result * * @author Michael Geramb - Initial contribution */ -@NonNullByDefault -public class JsonPushCommand { - public @Nullable String command; - public @Nullable String payload; +public class BehaviorOperationValidationResultTO { + public T operationPayload; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BluetoothPairedDeviceTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BluetoothPairedDeviceTO.java new file mode 100644 index 0000000000..814b46fac9 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/BluetoothPairedDeviceTO.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO; + +/** + * The {@link BluetoothPairedDeviceTO} encapsulate a part of {@link BluetoothStateTO} + * + * @author Jan N. Klug - Initial contribution + */ +public class BluetoothPairedDeviceTO { + public String address; + public boolean connected; + public String deviceClass; + public String friendlyName; + public List profiles = List.of(); + + @Override + public @NonNull String toString() { + return "BluetoothPairedDeviceTO{address='" + address + "', connected=" + connected + ", deviceClass='" + + deviceClass + "', friendlyName='" + friendlyName + "', profiles=" + profiles + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/CookieTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/CookieTO.java new file mode 100644 index 0000000000..ee5330da00 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/CookieTO.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link CookieTO} encapsulates a cookie + * + * @author Jan N. Klug - Initial contribution + */ +public class CookieTO { + @SerializedName("Path") + public String path; + @SerializedName("Secure") + public String secure; + @SerializedName("Value") + public String value; + @SerializedName("Expires") + public String expires; + @SerializedName("HttpOnly") + public String httpOnly; + @SerializedName("Name") + public String name; + + @Override + public @NonNull String toString() { + return "CookieTO{path='" + path + "', secure=" + secure + ", value='" + value + "', expires='" + expires + + "', httpOnly=" + httpOnly + ", name='" + name + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceIdTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceIdTO.java new file mode 100644 index 0000000000..37e1c9eada --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceIdTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link DeviceIdTO} encapsulates a target device for an announcement target + * + * @author Jan N. Klug - Initial contribution + */ +public class DeviceIdTO { + public String deviceSerialNumber; + public String deviceTypeId; + + @Override + public @NonNull String toString() { + return "AnnouncementTargetDeviceTO{deviceSerialNumber='" + deviceSerialNumber + "', deviceTypeId='" + + deviceTypeId + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceNotificationStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceNotificationStateTO.java new file mode 100644 index 0000000000..de6a3acb57 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceNotificationStateTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link DeviceNotificationStateTO} encapsulates a command to enable/disable ascending alarms on a device + * + * @author Jan N. Klug - Initial contribution + */ +public class DeviceNotificationStateTO { + public String deviceSerialNumber; + public String deviceType; + public String softwareVersion; + public int volumeLevel; + + @Override + public @NonNull String toString() { + return "DeviceNotificationStateTO{deviceSerialNumber='" + deviceSerialNumber + "', deviceType='" + deviceType + + "', softwareVersion='" + softwareVersion + "', volumeLevel=" + volumeLevel + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceTO.java new file mode 100644 index 0000000000..723ae94e0a --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DeviceTO.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link DeviceTO} encapsulate information about a single device + * + * @author Jan N. Klug - Initial contribution + */ +public class DeviceTO { + public String accountName; + public String serialNumber; + public String deviceOwnerCustomerId; + public String deviceAccountId; + public String deviceFamily; + public String deviceType; + public String softwareVersion; + public boolean online; + public Set capabilities = Set.of(); + + @Override + public @NonNull String toString() { + return "Device{accountName='" + accountName + "', serialNumber='" + serialNumber + "', deviceOwnerCustomerId='" + + deviceOwnerCustomerId + "', deviceAccountId='" + deviceAccountId + "', deviceFamily='" + deviceFamily + + "', deviceType='" + deviceType + "', softwareVersion='" + softwareVersion + "', online=" + online + + ", capabilities=" + capabilities + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DoNotDisturbDeviceStatusTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DoNotDisturbDeviceStatusTO.java new file mode 100644 index 0000000000..ad75c41ab3 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/DoNotDisturbDeviceStatusTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNull; + +/** + * The {@link DoNotDisturbDeviceStatusTO} encapsulates a command to enable/disable ascending alarms on a device + * + * @author Jan N. Klug - Initial contribution + */ +public class DoNotDisturbDeviceStatusTO { + public boolean enabled; + public String deviceSerialNumber; + public String deviceType; + @SerializeNull + public Object deviceAccountId = null; + + @Override + public @NonNull String toString() { + return "DoNotDisturbDeviceStatusTO{enabled=" + enabled + ", deviceSerialNumber='" + deviceSerialNumber + + "', deviceType='" + deviceType + "', deviceAccountId=" + deviceAccountId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedTO.java new file mode 100644 index 0000000000..964d0b434e --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link EnabledFeedTO} encapsulate a single feed + * + * @author Jan N. Klug - Initial contribution + */ +public class EnabledFeedTO { + public Object feedId; + public String name; + public String skillId; + public String imageUrl; + + @Override + public @NonNull String toString() { + return "EnabledFeedTO{feedId=" + feedId + ", name='" + name + "', skillId='" + skillId + "', imageUrl='" + + imageUrl + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationsResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedsTO.java similarity index 52% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationsResponse.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedsTO.java index 22c3640d58..1aad2b2d91 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationsResponse.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EnabledFeedsTO.java @@ -11,19 +11,22 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto; import java.util.List; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jdt.annotation.NonNull; /** - * The {@link JsonNotificationsResponse} encapsulate the GSON data for the result of a notifications request + * The {@link EnabledFeedsTO} encapsulate the data for handling enabled feeds * - * @author Michael Geramb - Initial contribution + * @author Jan N. Klug - Initial contribution */ -@NonNullByDefault -public class JsonNotificationsResponse { - public @Nullable List notifications; +public class EnabledFeedsTO { + public List enabledFeeds = List.of(); + + @Override + public @NonNull String toString() { + return "EnabledFeedsTO{enabledFeeds=" + enabledFeeds + "}"; + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EqualizerTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EqualizerTO.java new file mode 100644 index 0000000000..e1f97fa628 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/EqualizerTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link EqualizerTO} encapsulate an equalizer command/response + * + * @author Jan N. Klug - Initial contribution + */ +public class EqualizerTO { + public int bass = 0; + public int mid = 0; + public int treble = 0; + + @Override + public @NonNull String toString() { + return "JsonEqualizer{bass=" + bass + ", mid=" + mid + ", treble=" + treble + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationSoundTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationSoundTO.java new file mode 100644 index 0000000000..de2feb8cb1 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationSoundTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotificationSoundTO} encapsulate a notification sound + * + * @author Michael Geramb - Initial contribution + */ +public class NotificationSoundTO { + public String displayName; + public String folder; + public String id = "system_alerts_melodic_01"; + public String providerId = "ECHO"; + public String sampleUrl; + + @Override + public @NonNull String toString() { + return "NotificationSoundTO{displayName='" + displayName + "', folder='" + folder + "', id='" + id + + "', providerId='" + providerId + "', sampleUrl='" + sampleUrl + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationStateTO.java new file mode 100644 index 0000000000..9ebb42c5f9 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationStateTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotificationStateTO} encapsulates the request to set notification states + * + * @author Jan N. Klug - Initial contribution + */ +public class NotificationStateTO { + public String deviceSerialNumber; + public String deviceType; + public String softwareVersion; + public int volumeLevel; + + @Override + public @NonNull String toString() { + return "NotificationStateTO{deviceSerialNumber='" + deviceSerialNumber + "', deviceType='" + deviceType + + "', softwareVersion='" + softwareVersion + "', volumeLevel=" + volumeLevel + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationTO.java new file mode 100644 index 0000000000..c7f8365911 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/NotificationTO.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotificationTO} encapsulate a single notification + * + * @author Jan N. Klug - Initial contribution + */ +public class NotificationTO { + public String id; + public String type; + public String version; + + public String deviceSerialNumber; + public String deviceType; + + public long alarmTime; + public long createdDate; + public Object musicAlarmId; + public Object musicEntity; + public String notificationIndex; + public String originalDate; + public String originalTime; + public Object provider; + public boolean isRecurring; + public String recurringPattern; + public String reminderLabel; + public String reminderIndex; + public NotificationSoundTO sound = new NotificationSoundTO(); + public String status; + public String timeZoneId; + public String timerLabel; + public Object alarmIndex; + public boolean isSaveInFlight; + public Long triggerTime; + public Long remainingTime; + + @Override + public @NonNull String toString() { + return "NotificationTO{id='" + id + "', type='" + type + "', version='" + version + "', deviceSerialNumber='" + + deviceSerialNumber + "', deviceType='" + deviceType + "', alarmTime=" + alarmTime + ", createdDate=" + + createdDate + ", musicAlarmId=" + musicAlarmId + ", musicEntity=" + musicEntity + + ", notificationIndex='" + notificationIndex + "', originalDate='" + originalDate + "', originalTime='" + + originalTime + "', provider=" + provider + ", isRecurring=" + isRecurring + ", recurringPattern='" + + recurringPattern + "', reminderLabel='" + reminderLabel + "', reminderIndex='" + reminderIndex + + "', sound=" + sound + ", status='" + status + "', timeZoneId='" + timeZoneId + "', timerLabel='" + + timerLabel + "', alarmIndex=" + alarmIndex + ", isSaveInFlight=" + isSaveInFlight + ", triggerTime=" + + triggerTime + ", remainingTime=" + remainingTime + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlaySearchPhraseTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlaySearchPhraseTO.java new file mode 100644 index 0000000000..1e0d25f758 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlaySearchPhraseTO.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.reflect.TypeToken; + +/** + * The {@link PlaySearchPhraseTO} encapsulates the payload to validate a search phrase + * + * @author Jan N. Klug - Initial contribution + */ +public class PlaySearchPhraseTO { + @SuppressWarnings("unchecked") + public static final TypeToken> VALIDATION_RESULT_TO_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(BehaviorOperationValidationResultTO.class, PlaySearchPhraseTO.class); + + public String deviceType = "ALEXA_CURRENT_DEVICE_TYPE"; + public String deviceSerialNumber = "ALEXA_CURRENT_DSN"; + public String locale = "ALEXA_CURRENT_LOCALE"; + public String customerId; + public String searchPhrase; + public String sanitizedSearchPhrase; + public String musicProviderId = "ALEXA_CURRENT_DSN"; + + @Override + public @NonNull String toString() { + return "PlaySearchPhraseTO{deviceType='" + deviceType + "', deviceSerialNumber='" + deviceSerialNumber + + "', locale='" + locale + "', customerId='" + customerId + "', searchPhrase='" + searchPhrase + + "', sanitizedSearchPhrase='" + sanitizedSearchPhrase + "', musicProviderId='" + musicProviderId + + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTO.java new file mode 100644 index 0000000000..e699d28033 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTO.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link PlayerStateInfoTO} encapsulates the information about a player + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateInfoTO { + public String queueId; + public String mediaId; + @SerializedName(value = "state", alternate = { "playerState" }) + public String state; + public PlayerStateInfoTextTO infoText = new PlayerStateInfoTextTO(); + public PlayerStateInfoTextTO miniInfoText = new PlayerStateInfoTextTO(); + public PlayerStateProviderTO provider = new PlayerStateProviderTO(); + public PlayerStateVolumeTO volume = new PlayerStateVolumeTO(); + public PlayerStateMainArtTO mainArt = new PlayerStateMainArtTO(); + public PlayerStateProgressTO progress = new PlayerStateProgressTO(); + public PlayerStateMediaReferenceTO mediaReference = new PlayerStateMediaReferenceTO(); + + @Override + public @NonNull String toString() { + return "PlayerStateInfoTO{queueId='" + queueId + "', mediaId='" + mediaId + "', state='" + state + + "', infoText=" + infoText + ", miniInfoText=" + miniInfoText + ", provider=" + provider + ", volume=" + + volume + ", mainArt=" + mainArt + ", progress=" + progress + ", mediaReference=" + mediaReference + + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTextTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTextTO.java new file mode 100644 index 0000000000..7ad3a6435e --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateInfoTextTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateInfoTextTO} encapsulates the info text section of a player info + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateInfoTextTO { + public boolean multiLineMode; + public String subText1; + public String subText2; + public String title; + + @Override + public @NonNull String toString() { + return "PlayerStateInfoTextTO{multiLineMode=" + multiLineMode + ", subText1='" + subText1 + "', subText2='" + + subText2 + "', title='" + title + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMainArtTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMainArtTO.java new file mode 100644 index 0000000000..98ec40f095 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMainArtTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link PlayerStateMainArtTO} encapsulates the art section of a player info + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateMainArtTO { + public String altText; + public String artType; + public String contentType; + @SerializedName(value = "url", alternate = { "fullUrl" }) + public String url; + + @Override + public @NonNull String toString() { + return "PlayerStateMainArtTO{altText='" + altText + "', artType='" + artType + "', contentType='" + contentType + + "', url='" + url + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMediaReferenceTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMediaReferenceTO.java new file mode 100644 index 0000000000..3148ffeba6 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateMediaReferenceTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateMediaReferenceTO} encapsulates the media reference / queue information of a Player info state + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateMediaReferenceTO { + public String namespace; + public String name; + public String value; + + @Override + public @NonNull String toString() { + return "MediaReferenceTO{namespace='" + namespace + "', name='" + name + "', value='" + value + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProgressTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProgressTO.java new file mode 100644 index 0000000000..fd788f32c3 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProgressTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateProgressTO} encapsulates the progress section of a player info + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateProgressTO { + public boolean allowScrubbing; + public Object locationInfo; + public long mediaLength; + public long mediaProgress; + public boolean showTiming; + public boolean visible; + + @Override + public @NonNull String toString() { + return "PlayerStateProgressTO{allowScrubbing=" + allowScrubbing + ", locationInfo=" + locationInfo + + ", mediaLength=" + mediaLength + ", mediaProgress=" + mediaProgress + ", showTiming=" + showTiming + + ", visible=" + visible + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProviderTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProviderTO.java new file mode 100644 index 0000000000..c951c41c7d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateProviderTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateProviderTO} encapsulates the provider section of a player info + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateProviderTO { + public String providerDisplayName; + public String providerName; + + @Override + public @NonNull String toString() { + return "PlayerStateProviderTO{providerDisplayName='" + providerDisplayName + "', providerName='" + providerName + + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateVolumeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateVolumeTO.java new file mode 100644 index 0000000000..aabd31cdb8 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/PlayerStateVolumeTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateVolumeTO} encapsulates the volume part of a player info + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateVolumeTO { + public boolean muted; + public int volume; + + @Override + public @NonNull String toString() { + return "PlayerStateVolumeTO{muted=" + muted + ", volume=" + volume + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/TOMapper.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/TOMapper.java new file mode 100644 index 0000000000..b9d4c75d9f --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/TOMapper.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto; + +import static org.eclipse.jetty.util.StringUtil.isNotBlank; + +import java.net.HttpCookie; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.smarthomej.binding.amazonechocontrol.internal.types.Notification; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +/** + * The {@link TOMapper} contains mappers for TOs + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class TOMapper { + @SuppressWarnings("unchecked") + private static final TypeToken> MAP_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(Map.class, String.class, Object.class); + + private TOMapper() { + // prevent instantiation + } + + public static Map mapToMap(Gson gson, Object o) { + String json = gson.toJson(o); + return Objects.requireNonNullElse(gson.fromJson(json, MAP_TYPE_TOKEN), Map.of()); + } + + public static DeviceIdTO mapAnnouncementTargetDevice(DeviceTO device) { + DeviceIdTO targetDevice = new DeviceIdTO(); + targetDevice.deviceTypeId = device.deviceType; + targetDevice.deviceSerialNumber = device.serialNumber; + return targetDevice; + } + + public static CookieTO mapCookie(HttpCookie httpCookie) { + CookieTO cookie = new CookieTO(); + cookie.name = httpCookie.getName(); + cookie.value = httpCookie.getValue(); + cookie.secure = String.valueOf(httpCookie.getSecure()); + cookie.httpOnly = String.valueOf(httpCookie.isHttpOnly()); + return cookie; + } + + public static HttpCookie mapCookie(CookieTO cookie, String domain) { + HttpCookie httpCookie = new HttpCookie(cookie.name, cookie.value); + httpCookie.setPath(cookie.path); + httpCookie.setDomain(domain); + String secure = cookie.secure; + if (secure != null) { + httpCookie.setSecure(Boolean.getBoolean(secure)); + } + return httpCookie; + } + + public static @Nullable Notification map(NotificationTO notification, ZonedDateTime requestTime, + ZonedDateTime now) { + if (!"ON".equals(notification.status) || notification.deviceSerialNumber == null) { + return null; + } + ZonedDateTime alarmTime; + if ("Reminder".equals(notification.type) || "Alarm".equals(notification.type) + || "MusicAlarm".equals(notification.type)) { + LocalDate localDate = isNotBlank(notification.originalDate) ? LocalDate.parse(notification.originalDate) + : now.toLocalDate(); + LocalTime localTime = isNotBlank(notification.originalTime) ? LocalTime.parse(notification.originalTime) + : LocalTime.MIDNIGHT; + ZonedDateTime originalTime = ZonedDateTime.of(localDate, localTime, ZoneId.systemDefault()); + + if (notification.alarmTime == 0 || !isNotBlank(notification.recurringPattern)) { + alarmTime = originalTime; + } else { + // the alarm time needs to be DST adjusted + alarmTime = Instant.ofEpochMilli(notification.alarmTime).atZone(ZoneId.systemDefault()); + int alarmOffset = originalTime.getOffset().getTotalSeconds() - alarmTime.getOffset().getTotalSeconds(); + alarmTime = alarmTime.plusSeconds(alarmOffset); + } + } else if ("Timer".equals(notification.type) && notification.remainingTime > 0) { + alarmTime = requestTime.plus(notification.remainingTime, ChronoUnit.MILLIS); + } else { + return null; + } + return new Notification(notification.deviceSerialNumber, notification.type, alarmTime); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedTO.java new file mode 100644 index 0000000000..f8dabdc10d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedTO.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotifyMediaSessionsUpdatedTO} encapsulates NotifyMediaSessionsUpdated messages + * + * @author Jan N. Klug - Initial contribution + */ +public class NotifyMediaSessionsUpdatedTO { + private String customerId; + + private String name; + + private String messageId; + + private NotifyMediaSessionsUpdatedUpdateTO update; + + private boolean fallbackAllowed; + + @Override + public @NonNull String toString() { + return "NotifyMediaSessionsUpdatedTO{customerId='" + customerId + "', name='" + name + "', messageId='" + + messageId + "', update=" + update + ", fallbackAllowed=" + fallbackAllowed + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedUpdateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedUpdateTO.java new file mode 100644 index 0000000000..35ab41b8e6 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyMediaSessionsUpdatedUpdateTO.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotifyMediaSessionsUpdatedTO} encapsulates the inner update of NotifyMediaSessionsUpdated messages + * + * @author Jan N. Klug - Initial contribution + */ +public class NotifyMediaSessionsUpdatedUpdateTO { + public String type; + + @Override + public @NonNull String toString() { + return "NotifyMediaSessionsUpdatedUpdateTO{type='" + type + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedOuterUpdateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedOuterUpdateTO.java new file mode 100644 index 0000000000..389afb2e9d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedOuterUpdateTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotifyNowPlayingUpdatedOuterUpdateTO} encapsulates the outer update section of NotifyNowPlayingUpdated + * messages + * + * @author Jan N. Klug - Initial contribution + */ +public class NotifyNowPlayingUpdatedOuterUpdateTO { + + public String taskSessionId; + + public NotifyNowPlayingUpdatedUpdateTO update; + + public String type; + + @Override + public @NonNull String toString() { + return "NotifyNowPlayingUpdatedOuterUpdateTO{taskSessionId='" + taskSessionId + "', update=" + update + + ", type='" + type + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedTO.java new file mode 100644 index 0000000000..2fcc0ec8c8 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link NotifyNowPlayingUpdatedTO} encapsulates NotifyNowPlayingUpdated messages + * + * @author Jan N. Klug - Initial contribution + */ +public class NotifyNowPlayingUpdatedTO { + + public String customerId; + + public String name; + + public String messageId; + + public NotifyNowPlayingUpdatedOuterUpdateTO update; + + public boolean fallbackAllowed; + + @Override + public @NonNull String toString() { + return "NotifyNowPlayingUpdatedTO{customerId='" + customerId + "', name='" + name + "'" + ", messageId='" + + messageId + "', update=" + update + ", fallbackAllowed=" + fallbackAllowed + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedUpdateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedUpdateTO.java new file mode 100644 index 0000000000..772569cecf --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/NotifyNowPlayingUpdatedUpdateTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateInfoTO; + +/** + * The {@link NotifyNowPlayingUpdatedUpdateTO} encapsulates the inner update section of NotifyNowPlayingUpdated messages + * + * @author Jan N. Klug - Initial contribution + */ +public class NotifyNowPlayingUpdatedUpdateTO { + public boolean playbackError; + + public String errorMessage; + + public String cause; + + public String type; + + public PlayerStateInfoTO nowPlayingData; + + @Override + public @NonNull String toString() { + return "NotifyNowPlayingUpdatedUpdateTO{playbackError=" + playbackError + ", errorMessage='" + errorMessage + + "', cause='" + cause + "', type='" + type + "', nowPlayingData=" + nowPlayingData + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushAudioPlayerStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushAudioPlayerStateTO.java new file mode 100644 index 0000000000..7bdd54d8e7 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushAudioPlayerStateTO.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushAudioPlayerStateTO} encapsulates PUSH_AUDIO_PLAYER_STATE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushAudioPlayerStateTO extends PushDeviceTO { + public String mediaReferenceId; + public String quality; + public boolean error; + public AudioPlayerState audioPlayerState; + public String errorMessage; + + @Override + public @NonNull String toString() { + return "PushAudioplayerStateTO{mediaReferenceId='" + mediaReferenceId + "', error=" + error + + ", audioPlayerState=" + audioPlayerState + ", errorMessage='" + errorMessage + + "', destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + '}'; + } + + public enum AudioPlayerState { + INTERRUPTED, + FINISHED, + PLAYING + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushBluetoothStateChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushBluetoothStateChangeTO.java new file mode 100644 index 0000000000..ee8e20b592 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushBluetoothStateChangeTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushBluetoothStateChangeTO} encapsulates PUSH_BLUETOOTH_STATE_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushBluetoothStateChangeTO extends PushDeviceTO { + public String bluetoothEvent; + public String bluetoothEventPayload; + public boolean bluetoothEventSuccess; + + @Override + public @NonNull String toString() { + return "PushBluetoothStateChangeTO{bluetoothEvent='" + bluetoothEvent + "', bluetoothEventPayload='" + + bluetoothEventPayload + "', bluetoothEventSuccess=" + bluetoothEventSuccess + ", destinationUserId='" + + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushCommandTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushCommandTO.java new file mode 100644 index 0000000000..e0230cf191 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushCommandTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushCommandTO} encapsulates an activity stream command + * + * @author Jan N. Klug - Initial contribution + */ +public class PushCommandTO { + public String command; + public String payload; + public long timeStamp; + + @Override + public @NonNull String toString() { + return "CommandTO{command='" + command + "', payload='" + payload + "', timeStamp=" + timeStamp + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushContentFocusChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushContentFocusChangeTO.java new file mode 100644 index 0000000000..63aa1744d0 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushContentFocusChangeTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushContentFocusChangeTO} encapsulates PUSH_CONTENT_FOCUS_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushContentFocusChangeTO extends PushDeviceTO { + public String clientId; + public String deviceComponent; + + @Override + public @NonNull String toString() { + return "PushContentFocusChangeTO{clientId='" + clientId + "', deviceComponent='" + deviceComponent + + "', destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDevicePushConnectionChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDevicePushConnectionChangeTO.java new file mode 100644 index 0000000000..ef1ed4f67b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDevicePushConnectionChangeTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushDevicePushConnectionChangeTO} encapsulates PUSH_DOPPLER_CONNECTION_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushDevicePushConnectionChangeTO extends PushDeviceTO { + public DopplerConnectionState dopplerConnectionState; + + @Override + public @NonNull String toString() { + return "PushDopplerConnectionChangeTO{dopplerConnectionState=" + dopplerConnectionState + + ", destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } + + public enum DopplerConnectionState { + ONLINE, + OFFLINE + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDeviceTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDeviceTO.java new file mode 100644 index 0000000000..d3b2aaeac2 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDeviceTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushDeviceTO} encapsulates the header of a device/doppler message + * + * @author Jan N. Klug - Initial contribution + */ +public class PushDeviceTO { + public String destinationUserId; + public PushDopplerIdTO dopplerId; + + @Override + public @NonNull String toString() { + return "PushDeviceTO{destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDopplerIdTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDopplerIdTO.java new file mode 100644 index 0000000000..3ba9d04cbe --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushDopplerIdTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushDopplerIdTO} encapsulates the device information of activity messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushDopplerIdTO { + public String deviceSerialNumber; + public String deviceType; + + @Override + public @NonNull String toString() { + return "PushDopplerIdTO{deviceSerialNumber='" + deviceSerialNumber + "', deviceType='" + deviceType + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushEqualizerStateChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushEqualizerStateChangeTO.java new file mode 100644 index 0000000000..6b5c9bf558 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushEqualizerStateChangeTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushEqualizerStateChangeTO} encapsulates PUSH_EQUALIZER_STATE_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushEqualizerStateChangeTO extends PushDeviceTO { + public int bass; + public int midrange; + public int treble; + + @Override + public @NonNull String toString() { + return "PushEqualizerStateChangeTO{bass=" + bass + ", midrange=" + midrange + ", treble=" + treble + + ", destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaChangeTO.java new file mode 100644 index 0000000000..767dea0277 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaChangeTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushMediaChangeTO} encapsulates PUSH_MEDIA_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushMediaChangeTO extends PushDeviceTO { + public String mediaReferenceId; + + @Override + public @NonNull String toString() { + return "PushMediaChangeTO{mediaReferenceId='" + mediaReferenceId + "', destinationUserId='" + destinationUserId + + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaProgressChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaProgressChangeTO.java new file mode 100644 index 0000000000..f78ea2fd13 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaProgressChangeTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushMediaProgressChangeTO} encapsulates PUSH_MEDIA_PROGRESS_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushMediaProgressChangeTO extends PushDeviceTO { + public String mediaReferenceId; + public ProgressTO progress; + + public static class ProgressTO { + public long mediaProgress; + public long mediaLength; + } + + @Override + public @NonNull String toString() { + return "PushMediaProgressChangeTO{mediaReferenceId='" + mediaReferenceId + "', progress=" + progress + + ", destinationUserId='" + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaQueueChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaQueueChangeTO.java new file mode 100644 index 0000000000..7166d41648 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMediaQueueChangeTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link PushMediaQueueChangeTO} encapsulates PUSH_MEDIA_QUEUE_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushMediaQueueChangeTO extends PushDeviceTO { + public String changeType; + public @Nullable String playBackOrder; + public boolean trackOrderChanged; + public @Nullable String loopMode; + + @Override + public @NonNull String toString() { + return "PushMediaQueueChangeTO{changeType='" + changeType + "', playBackOrder='" + playBackOrder + "'" + + ", trackOrderChanged=" + trackOrderChanged + ", loopMode='" + loopMode + "', destinationUserId='" + + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMessageTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMessageTO.java new file mode 100644 index 0000000000..7c95c6f347 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushMessageTO.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link PushMessageTO} is used to handle activity messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushMessageTO { + + public DirectiveTO directive = new DirectiveTO(); + + @Override + public @NonNull String toString() { + return "MessageTO{directive=" + directive + "}"; + } + + public static class DirectiveTO { + public HeaderTO header = new HeaderTO(); + public PayloadTO payload = new PayloadTO(); + + @Override + public @NonNull String toString() { + return "DirectiveTO{header=" + header + ", payload=" + payload + "}"; + } + } + + public static class HeaderTO { + public String namespace; + @SerializedName("name") + public String directiveName; + public String messageId; + + @Override + public @NonNull String toString() { + return "HeaderTO{namespace='" + namespace + "', directiveName='" + directiveName + "', messageId='" + + messageId + "'}"; + } + } + + public static class PayloadTO { + public List renderingUpdates = List.of(); + + @Override + public @NonNull String toString() { + return "PayloadTO{renderingUpdates=" + renderingUpdates + "}"; + } + } + + public static class RenderingUpdateTO { + public String route; + public String resourceId; + public String resourceMetadata; + + @Override + public @NonNull String toString() { + return "RenderingUpdateTO{route='" + route + "', resourceId='" + resourceId + "', resourceMetadata='" + + resourceMetadata + "'}"; + } + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushNotificationChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushNotificationChangeTO.java new file mode 100644 index 0000000000..3fba2e2247 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushNotificationChangeTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushNotificationChangeTO} encapsulates PUSH_NOTIFICATION_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushNotificationChangeTO extends PushDeviceTO { + public String eventType; + public String notificationId; + public int notificationVersion; + + @Override + public @NonNull String toString() { + return "PushNotificationChangeTO{eventType='" + eventType + "', notificationId='" + notificationId + + "', notificationVersion=" + notificationVersion + ", destinationUserId='" + destinationUserId + + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushVolumeChangeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushVolumeChangeTO.java new file mode 100644 index 0000000000..9242400ff7 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/push/PushVolumeChangeTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.push; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PushVolumeChangeTO} encapsulates PUSH_VOLUME_CHANGE messages + * + * @author Jan N. Klug - Initial contribution + */ +public class PushVolumeChangeTO extends PushDeviceTO { + public boolean isMuted; + public int volumeSetting; + + @Override + public @NonNull String toString() { + return "PushVolumeChangeTO{isMuted=" + isMuted + ", volumeSetting=" + volumeSetting + ", destinationUserId='" + + destinationUserId + "', dopplerId=" + dopplerId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementContentTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementContentTO.java new file mode 100644 index 0000000000..87b568434f --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementContentTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AnnouncementContentTO} encapsulate the content of an announcement + * announcements + * + * @author Jan N. Klug - Initial contribution + */ +public class AnnouncementContentTO { + public String locale = ""; + public AnnouncementDisplayTO display = new AnnouncementDisplayTO(); + public AnnouncementSpeakTO speak = new AnnouncementSpeakTO(); + + @Override + public @NonNull String toString() { + return "AnnouncementContentTO{locale='" + locale + "'" + ", display=" + display + ", speak=" + speak + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementDisplayTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementDisplayTO.java new file mode 100644 index 0000000000..29cb6a22db --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementDisplayTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AnnouncementDisplayTO} encapsulates the display part of an announcement + * + * @author Jan N. Klug - Initial contribution + */ +public class AnnouncementDisplayTO { + public String title; + public String body; + + @Override + public @NonNull String toString() { + return "AnnouncementDisplayTO{title='" + title + "', body='" + body + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementSpeakTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementSpeakTO.java new file mode 100644 index 0000000000..36b0e43399 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementSpeakTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AnnouncementSpeakTO} encapsulates the speak part of an announcement + * + * @author Jan N. Klug - Initial contribution + */ +public class AnnouncementSpeakTO { + public String type; + public String value; + + @Override + public @NonNull String toString() { + return "AnnouncementSpeakTO{type='" + type + "', value='" + value + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTO.java new file mode 100644 index 0000000000..6c67b899e4 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AnnouncementTO} encapsulates an announcement + * + * @author Jan N. Klug - Initial contribution + */ +public class AnnouncementTO { + public String expireAfter = "PT5S"; + public List content = List.of(); + public AnnouncementTargetTO target = new AnnouncementTargetTO(); + public String customerId; + + @Override + public @NonNull String toString() { + return "AnnouncementTO{expireAfter='" + expireAfter + "', content=" + content + ", target=" + target + + ", customerId='" + customerId + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTargetTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTargetTO.java new file mode 100644 index 0000000000..0fe6ee004f --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AnnouncementTargetTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceIdTO; + +/** + * The {@link AnnouncementTargetTO} encapsulate the target section of an announcement + * + * @author Jan N. Klug - Initial contribution + */ +public class AnnouncementTargetTO { + public String customerId; + public List devices = List.of(); + + @Override + public @NonNull String toString() { + return "AnnouncementTargetTO{customerId='" + customerId + "', devices=" + devices + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterAuthTO.java similarity index 53% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterAuthTO.java index 136479713c..1aa43aa834 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterAuthTO.java @@ -1,5 +1,4 @@ /** - * Copyright (c) 2010-2021 Contributors to the openHAB project * Copyright (c) 2021-2023 Contributors to the SmartHome/J project * * See the NOTICE file(s) distributed with this work for additional @@ -11,24 +10,24 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.request; -import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import com.google.gson.annotations.SerializedName; /** - * The {@link JsonTokenResponse} encapsulate the GSON data of the token response + * The {@link AuthRegisterAuthTO} encapsulates the auth information of an app registration request * - * @author Michael Geramb - Initial contribution + * @author Jan N. Klug - Initial contribution */ -@NonNullByDefault -public class JsonTokenResponse { +public class AuthRegisterAuthTO { @SerializedName("access_token") public @Nullable String accessToken; - @SerializedName("token_type") - public @Nullable String tokenType; - @SerializedName("expires_in") - public @Nullable Integer expiresIn; + + @Override + public @NonNull String toString() { + return "AuthRegisterAuthTO{accessToken='" + accessToken + "'}"; + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterCookiesTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterCookiesTO.java new file mode 100644 index 0000000000..acdcdcd224 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterCookiesTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.CookieTO; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterCookiesTO} encapsulates the cookie information for a given domain + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterCookiesTO { + @SerializedName("website_cookies") + public List webSiteCookies = List.of(); + public String domain = ".amazon.com"; + + @Override + public @NonNull String toString() { + return "AuthRegisterCookiesTO{webSiteCookies=" + webSiteCookies + ", domain='" + domain + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterRegistrationTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterRegistrationTO.java new file mode 100644 index 0000000000..58bd795391 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterRegistrationTO.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.API_VERSION; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DI_OS_VERSION; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterRegistrationTO} encapsulates the registration data for an app registration request + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterRegistrationTO { + public String domain = "Device"; + @SerializedName("app_version") + public String appVersion = API_VERSION; + @SerializedName("device_type") + public String deviceType = "A2IVLV5VM2W81"; + @SerializedName("device_name") + public String deviceName = "%FIRST_NAME%'s%DUPE_STRATEGY_1ST%openHAB Alexa Binding"; + @SerializedName("os_version") + public String osVersion = DI_OS_VERSION; + @SerializedName("device_serial") + public @Nullable String deviceSerial; + @SerializedName("device_model") + public String deviceModel = "iPhone"; + @SerializedName("app_name") + public String appName = "openHAB Alexa Binding"; + @SerializedName("software_version") + public String softwareVersion = "1"; + + @Override + public @NonNull String toString() { + return "AuthRegisterRegistrationTO{domain='" + domain + "', appVersion='" + appVersion + "', deviceType='" + + deviceType + "', deviceName='" + deviceName + "', osVersion='" + osVersion + "', deviceSerial='" + + deviceSerial + "', deviceModel='" + deviceModel + "', appName='" + appName + "', softwareVersion='" + + softwareVersion + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterTO.java new file mode 100644 index 0000000000..26d009e06b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/AuthRegisterTO.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterTO} encapsulate the app registration request + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterTO { + @SerializedName("requested_extensions") + public List requestedExtensions = List.of("device_info", "customer_info"); + public AuthRegisterCookiesTO cookies = new AuthRegisterCookiesTO(); + @SerializedName("registration_data") + public AuthRegisterRegistrationTO registrationData = new AuthRegisterRegistrationTO(); + @SerializedName("auth_data") + public AuthRegisterAuthTO authData = new AuthRegisterAuthTO(); + @SerializedName("user_context_map") + public Map userContextMap = Map.of(); + @SerializedName("requested_token_type") + public List requestedTokenType = List.of("bearer", "mac_dms", "website_cookies"); + + @Override + public @NonNull String toString() { + return "AuthRegisterTO{requestedExtensions=" + requestedExtensions + ", cookies=" + cookies + + ", registrationData=" + registrationData + ", authData=" + authData + ", userContextMap=" + + userContextMap + ", requestedTokenType=" + requestedTokenType + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/BehaviorOperationValidateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/BehaviorOperationValidateTO.java new file mode 100644 index 0000000000..d0cdf7fb94 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/BehaviorOperationValidateTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link BehaviorOperationValidateTO} encapsulates a behavior validation request + * + * @author Jan N. Klug - Initial contribution + */ +public class BehaviorOperationValidateTO { + public String type; + public String operationPayload; + + @Override + public @NonNull String toString() { + return "BehaviorOperationValidateTO{type='" + type + "', operationPayload='" + operationPayload + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenResponseTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenResponseTO.java new file mode 100644 index 0000000000..f89e2b3191 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenResponseTO.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link ExchangeTokenResponseTO} encapsulates the response of an exchange token request + * + * @author Jan N. Klug - Initial contribution + */ +public class ExchangeTokenResponseTO { + public ExchangeTokenTokensTO tokens = new ExchangeTokenTokensTO(); + + @Override + public @NonNull String toString() { + return "ExchangeTokenResponseTO{tokens=" + tokens + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTO.java new file mode 100644 index 0000000000..6f354c6b97 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTO.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link ExchangeTokenTO} encapsulates an exchange token request + * + * @author Jan N. Klug - Initial contribution + */ +public class ExchangeTokenTO { + public ExchangeTokenResponseTO response = new ExchangeTokenResponseTO(); + + @Override + public @NonNull String toString() { + return "ExchangeTokenTO{response=" + response + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTokensTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTokensTO.java new file mode 100644 index 0000000000..edc4af362f --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/ExchangeTokenTokensTO.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.CookieTO; + +/** + * The {@link ExchangeTokenTokensTO} encapsulates the token section of an exchange token request + * + * @author Jan N. Klug - Initial contribution + */ +public class ExchangeTokenTokensTO { + public Map> cookies = new HashMap<>(); + + @Override + public @NonNull String toString() { + return "ExchangeTokenTokensTO{cookies=" + cookies + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/PlayerSeekMediaTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/PlayerSeekMediaTO.java new file mode 100644 index 0000000000..41cb7b46d3 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/PlayerSeekMediaTO.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNull; + +/** + * The {@link PlayerSeekMediaTO} encapsulates a command to seek in a media file + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerSeekMediaTO { + public String type = "SeekCommand"; + public long mediaPosition; + @SerializeNull + public Object contentFocusClientId = null; + + @Override + public @NonNull String toString() { + return "PlayerSeekMediaTO{type='" + type + "', mediaPosition=" + mediaPosition + ", contentFocusClientId=" + + contentFocusClientId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/SendConversationDTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/SendConversationDTO.java new file mode 100644 index 0000000000..f9c108daa9 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/SendConversationDTO.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link SendConversationDTO} encapsulates a new message to all devices + * + * @author Jan N. Klug - Initial contribution + */ +public class SendConversationDTO { + public String conversationId; + public String clientMessageId; + public int messageId; + public String time; + public String sender; + public String type = "message/text"; + public Map payload = new HashMap<>(); + public int status = 1; + + @Override + public @NonNull String toString() { + return "SendConversationDTO{conversationId='" + conversationId + "', clientMessageId='" + clientMessageId + + "', messageId=" + messageId + ", nextAlarmTime='" + time + "', sender='" + sender + "', type='" + type + + "', payload=" + payload + ", status=" + status + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/StartRoutineTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/StartRoutineTO.java new file mode 100644 index 0000000000..3cf12a7aa6 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/StartRoutineTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link StartRoutineTO} encapsulate the request to start a routine + * + * @author Jan N. Klug - Initial contribution + */ +public class StartRoutineTO { + public String behaviorId = "PREVIEW"; + public String sequenceJson; + public String status = "ENABLED"; + + @Override + public @NonNull String toString() { + return "StartRoutineTO{behaviorId='" + behaviorId + "', sequenceJson='" + sequenceJson + "', status='" + status + + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/WHAVolumeLevelTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/WHAVolumeLevelTO.java new file mode 100644 index 0000000000..074b86d5bd --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/request/WHAVolumeLevelTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.request; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link WHAVolumeLevelTO} encapsulates a command to set the WHA volume + * + * @author Jan N. Klug - Initial contribution + */ +public class WHAVolumeLevelTO { + public String type = "VolumeLevelCommand"; + public int volumeLevel; + public Object contentFocusClientId = "Default"; + + @Override + public @NonNull String toString() { + return "WHAVolumeLevelTO{type='" + type + "', volumeLevel=" + volumeLevel + ", contentFocusClientId=" + + contentFocusClientId + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AccountTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AccountTO.java new file mode 100644 index 0000000000..dc8588ec76 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AccountTO.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.reflect.TypeToken; + +/** + * The {@link AccountTO} encapsulates the account information + * + * @author Jan N. Klug - Initial contribution + */ +@SuppressWarnings("unused") +public class AccountTO { + @SuppressWarnings("unchecked") + public static final TypeToken> LIST_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(List.class, AccountTO.class); + + public String commsId; + public String directedId; + public String phoneCountryCode; + public String phoneNumber; + public String firstName; + public String lastName; + public String phoneticFirstName; + public String phoneticLastName; + public String commsProvisionStatus; + public Boolean isChild; + public Boolean signedInUser; + public Boolean commsProvisioned; + public Boolean speakerProvisioned; + + @Override + public @NonNull String toString() { + return "AccountTO{commsId='" + commsId + "', directedId='" + directedId + "', phoneCountryCode='" + + phoneCountryCode + "', phoneNumber='" + phoneNumber + "', firstName='" + firstName + "', lastName='" + + lastName + "', phoneticFirstName='" + phoneticFirstName + "', phoneticLastName='" + phoneticLastName + + "', commsProvisionStatus='" + commsProvisionStatus + "', isChild=" + isChild + ", signedInUser=" + + signedInUser + ", commsProvisioned=" + commsProvisioned + ", speakerProvisioned=" + speakerProvisioned + + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AscendingAlarmModelsTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AscendingAlarmModelsTO.java new file mode 100644 index 0000000000..b5227735cf --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AscendingAlarmModelsTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.AscendingAlarmModelTO; + +/** + * The {@link AscendingAlarmModelsTO} encapsulates the response of the /api/ascending-alarm + * + * @author Jan N. Klug - Initial contribution + */ +public class AscendingAlarmModelsTO { + public List ascendingAlarmModelList = List.of(); + + @Override + public @NonNull String toString() { + return "AscendingAlarmModelsTO{ascendingAlarmModelList=" + ascendingAlarmModelList + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterBearerTokenTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterBearerTokenTO.java new file mode 100644 index 0000000000..39bdae6923 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterBearerTokenTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterBearerTokenTO} encapsulates the bearer token information + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterBearerTokenTO { + @SerializedName("access_token") + public String accessToken; + + @SerializedName("refresh_token") + public String refreshToken; + + @SerializedName("expires_in") + public String expiresIn; + + @Override + public @NonNull String toString() { + return "AuthRegisterBearerTO{accessToken='" + accessToken + "', refreshToken='" + refreshToken + + "', expiresIn='" + expiresIn + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterCustomerInfoTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterCustomerInfoTO.java new file mode 100644 index 0000000000..95ea265100 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterCustomerInfoTO.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterCustomerInfoTO} encapsulates the customer information of a registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterCustomerInfoTO { + @SerializedName("account_pool") + public String accountPool; + @SerializedName("user_id") + public String userId; + @SerializedName("home_region") + public String homeRegion; + public String name; + @SerializedName("given_name") + public String givenName; + + @Override + public @NonNull String toString() { + return "AuthRegisterCustomerInfoTO{accountPool='" + accountPool + "', userId='" + userId + "', homeRegion='" + + homeRegion + "', name='" + name + "', givenName='" + givenName + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterDeviceInfoTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterDeviceInfoTO.java new file mode 100644 index 0000000000..8e1a6ab109 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterDeviceInfoTO.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterDeviceInfoTO} encapsulates the device information of an app registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterDeviceInfoTO { + @SerializedName("device_name") + public String deviceName = "Unknown"; + @SerializedName("device_serial_number") + public String deviceSerialNumber; + @SerializedName("device_type") + public String deviceType; + + @Override + public @NonNull String toString() { + return "AuthRegisterDeviceInfoTO{deviceName='" + deviceName + "', deviceSerialNumber='" + deviceSerialNumber + + "', deviceType='" + deviceType + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterExtensionsTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterExtensionsTO.java new file mode 100644 index 0000000000..e3bae82a10 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterExtensionsTO.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterExtensionsTO} encapsulates the extension part of an app registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterExtensionsTO { + @SerializedName("device_info") + public AuthRegisterDeviceInfoTO deviceInfo = new AuthRegisterDeviceInfoTO(); + @SerializedName("customer_info") + public AuthRegisterCustomerInfoTO customerInfo = new AuthRegisterCustomerInfoTO(); + @SerializedName("customer_id") + public @Nullable String customerId; + + @Override + public @NonNull String toString() { + return "AuthRegisterExtensions" + "TO{deviceInfo=" + deviceInfo + ", customerInfo=" + customerInfo + + ", customerId='" + customerId + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterMacDmsTokenTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterMacDmsTokenTO.java new file mode 100644 index 0000000000..9b3b3fad21 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterMacDmsTokenTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterMacDmsTokenTO} encapsulates MAC dms tokens + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterMacDmsTokenTO { + @SerializedName("device_private_key") + public String devicePrivateKey; + + @SerializedName("adp_token") + public String adpToken; + + @Override + public @NonNull String toString() { + return "AuthRegisterMacDmsTokenTO{devicePrivateKey='" + devicePrivateKey + "', adpToken='" + adpToken + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterResponseTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterResponseTO.java new file mode 100644 index 0000000000..7595b88c4b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterResponseTO.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AuthRegisterResponseTO} encapsulates the internal response section of an app registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterResponseTO { + public AuthRegisterSuccessTO success = new AuthRegisterSuccessTO(); + + @Override + public @NonNull String toString() { + return "AuthRegisterResponseTO{success=" + success + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterSuccessTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterSuccessTO.java new file mode 100644 index 0000000000..6962cbf6fb --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterSuccessTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterSuccessTO} encapsulates the success section of an app registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterSuccessTO { + public AuthRegisterExtensionsTO extensions = new AuthRegisterExtensionsTO(); + public AuthRegisterTokensTO tokens = new AuthRegisterTokensTO(); + @SerializedName("customer_id") + public String customerId; + + @Override + public @NonNull String toString() { + return "AuthRegisterSuccessTO{extensions=" + extensions + ", tokens=" + tokens + ", customerId='" + customerId + + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTO.java new file mode 100644 index 0000000000..6fb4b54b98 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterTO} encapsulate an app registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterTO { + @SerializedName("request_id") + public String requestId; + public AuthRegisterResponseTO response = new AuthRegisterResponseTO(); + + @Override + public @NonNull String toString() { + return "AuthRegister{requestId='" + requestId + "', response=" + response + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTokensTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTokensTO.java new file mode 100644 index 0000000000..c741ee5540 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthRegisterTokensTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.CookieTO; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link AuthRegisterTokensTO} encapsulates all tokens for a connection registration response + * + * @author Jan N. Klug - Initial contribution + */ +public class AuthRegisterTokensTO { + @SerializedName("website_cookies") + public List websiteCookies = List.of(); + @SerializedName("mac_dms") + public AuthRegisterMacDmsTokenTO macDms = new AuthRegisterMacDmsTokenTO(); + public AuthRegisterBearerTokenTO bearer = new AuthRegisterBearerTokenTO(); + + @Override + public @NonNull String toString() { + return "AuthRegisterTokensTO{websiteCookies=" + websiteCookies + ", macDms=" + macDms + ", bearer=" + bearer + + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthTokenTO.java similarity index 50% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthTokenTO.java index 6678f01dd3..56b06af387 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AuthTokenTO.java @@ -1,5 +1,4 @@ /** - * Copyright (c) 2010-2021 Contributors to the openHAB project * Copyright (c) 2021-2023 Contributors to the SmartHome/J project * * See the NOTICE file(s) distributed with this work for additional @@ -11,24 +10,28 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.response; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jdt.annotation.NonNull; import com.google.gson.annotations.SerializedName; /** - * The {@link JsonRenewTokenResponse} encapsulate the GSON response of the renew token request + * The {@link AuthTokenTO} encapsulates the response of a request to /auth/token * - * @author Michael Geramb - Initial contribution + * @author Jan N. Klug - Initial contribution */ -@NonNullByDefault -public class JsonRenewTokenResponse { +public class AuthTokenTO { @SerializedName("access_token") - public @Nullable String accessToken; + public String accessToken; @SerializedName("token_type") - public @Nullable String tokenType; + public String tokenType; @SerializedName("expires_in") - public @Nullable Long expiresIn; + public long expiresIn; + + @Override + public @NonNull String toString() { + return "AuthTokenTO{accessToken='" + accessToken + "', tokenType='" + tokenType + "', expiresIn=" + expiresIn + + "}"; + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationPayloadTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationPayloadTO.java new file mode 100644 index 0000000000..105e9bc5a4 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationPayloadTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AutomationPayloadTO} encapsulates the payload section of n automation + * + * @author Jan N. Klug - Initial contribution + */ +public class AutomationPayloadTO { + public String customerId; + public String utterance; + public String locale; + public String marketplaceId; + + @Override + public @NonNull String toString() { + return "AutomationPayloadTO{customerId='" + customerId + "', utterance='" + utterance + "', locale='" + locale + + "', marketplaceId='" + marketplaceId + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTO.java new file mode 100644 index 0000000000..7d0af944ef --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTO.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.reflect.TypeToken; + +/** + * The {@link AutomationTO} encapsulates a single routine/automation + * + * @author Jan N. Klug - Initial contribution + */ +public class AutomationTO { + @SuppressWarnings("unchecked") + public static final TypeToken> LIST_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(List.class, AutomationTO.class); + public String automationId; + public String name; + public List triggers = List.of(); + public TreeMap sequence; + public String status; + public long creationTimeEpochMillis; + public long lastUpdatedTimeEpochMillis; + + @Override + public @NonNull String toString() { + return "AutomationTO{automationId='" + automationId + "', name='" + name + "', triggers=" + triggers + + ", sequence=" + sequence + ", status='" + status + "', creationTimeEpochMillis=" + + creationTimeEpochMillis + ", lastUpdatedTimeEpochMillis=" + lastUpdatedTimeEpochMillis + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTriggerTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTriggerTO.java new file mode 100644 index 0000000000..0bebabfd25 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/AutomationTriggerTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link AutomationTriggerTO} encapsulates the trigger section of an automation + * + * @author Jan N. Klug - Initial contribution + */ +public class AutomationTriggerTO { + public AutomationPayloadTO payload = new AutomationPayloadTO(); + public String id; + public String type; + + @Override + public @NonNull String toString() { + return "AutomationTriggerTO{payload=" + payload + ", id='" + id + "', type='" + type + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStateTO.java new file mode 100644 index 0000000000..e95c5bdf32 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStateTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.BluetoothPairedDeviceTO; + +/** + * The {@link BluetoothStateTO} encapsulate a single bluetoth state + * + * @author Jan N. Klug - Initial contribution + */ +public class BluetoothStateTO { + public String deviceSerialNumber; + public String deviceType; + public String friendlyName; + public boolean gadgetPaired; + public boolean online; + public List pairedDeviceList = List.of(); + + @Override + public @NonNull String toString() { + return "BluetoothStateTO{deviceSerialNumber='" + deviceSerialNumber + "', deviceType='" + deviceType + + "', friendlyName='" + friendlyName + "', gadgetPaired=" + gadgetPaired + ", online=" + online + + ", pairedDeviceList=" + pairedDeviceList + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStatesTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStatesTO.java new file mode 100644 index 0000000000..77167c9206 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BluetoothStatesTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link BluetoothStatesTO} encapsulate the response of /api/bluetooth + * + * @author Jan N. Klug - Initial contribution + */ +public class BluetoothStatesTO { + public List bluetoothStates = List.of(); + + @Override + public @NonNull String toString() { + return "BluetoothStatesTO{bluetoothStates=" + bluetoothStates + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapAuthenticationTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapAuthenticationTO.java new file mode 100644 index 0000000000..0a4f2e4d97 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapAuthenticationTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link BootstrapAuthenticationTO} encapsulates the authentication information of a bootstrap response + * + * @author Jan N. Klug - Initial contribution + */ +public class BootstrapAuthenticationTO { + public boolean authenticated; + public boolean canAccessPrimeMusicContent; + public String customerEmail; + public String customerId; + public String customerName; + + @Override + public @NonNull String toString() { + return "BootstrapAuthenticationTO{authenticated=" + authenticated + ", canAccessPrimeMusicContent=" + + canAccessPrimeMusicContent + ", customerEmail='" + customerEmail + "', customerId='" + customerId + + "', customerName='" + customerName + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapTO.java new file mode 100644 index 0000000000..4cba1f9a9c --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/BootstrapTO.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link BootstrapTO} encapsulate the response of a boostrap request + * + * @author Jan N. Klug - Initial contribution + */ +public class BootstrapTO { + public BootstrapAuthenticationTO authentication; + + @Override + public @NonNull String toString() { + return "BootstrapTO{authentication=" + authentication + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordTO.java new file mode 100644 index 0000000000..18703a5f25 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordTO.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link CustomerHistoryRecordTO} encapsulates + * + * @author Jan N. Klug - Initial contribution + */ +public class CustomerHistoryRecordTO { + public String recordKey; + public String recordType; + public long timestamp; + public String customerId; + public Object device; + public boolean isBinaryFeedbackProvided; + public boolean isFeedbackPositive; + public String utteranceType; + public String domain; + public String intent; + public String skillName; + public List voiceHistoryRecordItems = List.of(); + public List personsInfo = List.of(); + + @Override + public @NonNull String toString() { + return "CustomerHistoryRecordTO{recordKey='" + recordKey + "', recordType='" + recordType + "', timestamp=" + + timestamp + ", customerId='" + customerId + "', device=" + device + ", isBinaryFeedbackProvided=" + + isBinaryFeedbackProvided + ", isFeedbackPositive=" + isFeedbackPositive + ", utteranceType='" + + utteranceType + "', domain='" + domain + "', intent='" + intent + "', skillName='" + skillName + + "', voiceHistoryRecordItems=" + voiceHistoryRecordItems + ", personsInfo=" + personsInfo + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordVoiceTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordVoiceTO.java new file mode 100644 index 0000000000..28d686fb4d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordVoiceTO.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link CustomerHistoryRecordVoiceTO} encapsulates a voice history record of a customer history + * + * @author Jan N. Klug - Initial contribution + */ +public class CustomerHistoryRecordVoiceTO { + public String recordItemKey; + public String recordItemType; + public String utteranceId; + public long timestamp; + public String transcriptText; + public String agentVisualName; + public List personsInfo = List.of(); + + @Override + public @NonNull String toString() { + return "CustomerHistoryRecordVoiceTO{recordItemKey='" + recordItemKey + "', recordItemType='" + recordItemType + + "', utteranceId='" + utteranceId + "', timestamp=" + timestamp + ", transcriptText='" + transcriptText + + "', agentVisualName='" + agentVisualName + "', personsInfo=" + personsInfo + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordsTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordsTO.java new file mode 100644 index 0000000000..89ee2e9c8d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/CustomerHistoryRecordsTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link CustomerHistoryRecordsTO} encapsulate the response for customer history record requests + * + * @author Jan N. Klug - Initial contribution + */ +public class CustomerHistoryRecordsTO { + public List customerHistoryRecords = List.of(); + + @Override + public @NonNull String toString() { + return "CustomerHistoryRecordsTO{customerHistoryRecords=" + customerHistoryRecords + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceListTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceListTO.java new file mode 100644 index 0000000000..f915312581 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceListTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; + +/** + * The {@link DeviceListTO} encapsulate the response of /api/devices-v2 + * + * @author Jan N. Klug - Initial contribution + */ +public class DeviceListTO { + public List devices = List.of(); + + @Override + public @NonNull String toString() { + return "DeviceListTO{devices=" + devices + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceNotificationStatesTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceNotificationStatesTO.java new file mode 100644 index 0000000000..fbf8067749 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DeviceNotificationStatesTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceNotificationStateTO; + +/** + * The {@link DeviceNotificationStatesTO} encapsulate the response of the /api/device-notification-state + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class DeviceNotificationStatesTO { + public List deviceNotificationStates = List.of(); + + @Override + public @NonNull String toString() { + return "DeviceNotificationStatesTO{deviceNotificationStates=" + deviceNotificationStates + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DoNotDisturbDeviceStatusesTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DoNotDisturbDeviceStatusesTO.java new file mode 100644 index 0000000000..96ba844140 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/DoNotDisturbDeviceStatusesTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DoNotDisturbDeviceStatusTO; + +/** + * The {@link DoNotDisturbDeviceStatusesTO} encapsulate the response of /api/dnd/device-status-list + * + * @author Jan N. Klug - Initial contribution + */ +public class DoNotDisturbDeviceStatusesTO { + public List doNotDisturbDeviceStatusList = List.of(); + + @Override + public @NonNull String toString() { + return "DoNotDisturbDeviceStatusesTO{doNotDisturbDeviceStatusList=" + doNotDisturbDeviceStatusList + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EligibilityTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EligibilityTO.java new file mode 100644 index 0000000000..acf1532d6d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EligibilityTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link EligibilityTO} encapsulates the eligibility section of a media session + * + * @author Jan N. Klug - Initial contribution + */ +public class EligibilityTO { + public boolean isEligible; + public String reasonCode; + + @Override + public @NonNull String toString() { + return "EligibilityTO{isEligible=" + isEligible + ", reasonCode='" + reasonCode + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EndpointTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EndpointTO.java new file mode 100644 index 0000000000..01dd74eac9 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/EndpointTO.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link DoNotDisturbDeviceStatusesTO} encapsulate the response of /api/endpoints + * + * @author Jan N. Klug - Initial contribution + */ +public class EndpointTO { + public String alexaApiUrl; + public String awsRegion; + public String retailDomain; + public String retailUrl; + public String skillsStoreUrl; + public String websiteApiUrl; + public String websiteUrl; + + @Override + public @NonNull String toString() { + return "EndpointTO{alexaApiUrl='" + alexaApiUrl + "', awsRegion='" + awsRegion + "', retailDomain='" + + retailDomain + "', retailUrl='" + retailUrl + "', skillsStoreUrl='" + skillsStoreUrl + + "', websiteApiUrl='" + websiteApiUrl + "', websiteUrl='" + websiteUrl + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/ListMediaSessionTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/ListMediaSessionTO.java new file mode 100644 index 0000000000..a04ce9305b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/ListMediaSessionTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link ListMediaSessionTO} encapsulates the response to /api/np/list-media.sessions + * + * @author Jan N. Klug - Initial contribution + */ +public class ListMediaSessionTO { + public List mediaSessionList = List.of(); + + @Override + public @NonNull String toString() { + return "ListMediaSessionTO{mediaSessionList=" + mediaSessionList + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionEndpointTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionEndpointTO.java new file mode 100644 index 0000000000..ce6d33ac09 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionEndpointTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceIdTO; + +import com.google.gson.annotations.SerializedName; + +/** + * The {@link MediaSessionEndpointTO} encapsulates a single endpoint information for a media session + * + * @author Jan N. Klug - Initial contribution + */ +public class MediaSessionEndpointTO { + @SerializedName("__type") + public String type; + public String encryptedFriendlyName; + public DeviceIdTO id; + + @Override + public @NonNull String toString() { + return "MediaSessionEndpointTO{type='" + type + "', encryptedFriendlyName='" + encryptedFriendlyName + "', id=" + + id + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionTO.java new file mode 100644 index 0000000000..bbd349d184 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaSessionTO.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateInfoTO; + +/** + * The {@link MediaSessionTO} encapsulates a single media session + * + * @author Jan N. Klug - Initial contribution + */ +public class MediaSessionTO { + public EligibilityTO castEligibility; + public List endpointList = List.of(); + public PlayerStateInfoTO nowPlayingData; + + @Override + public @NonNull String toString() { + return "MediaSessionTO{castEligibility=" + castEligibility + ", endpointList=" + endpointList + + ", nowPlayingData=" + nowPlayingData + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateQueueEntryTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateQueueEntryTO.java new file mode 100644 index 0000000000..43a4940e92 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateQueueEntryTO.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link MediaStateQueueEntryTO} encapsulates a queue entry in media content state + * + * @author Jan N. Klug - Initial contribution + */ +public class MediaStateQueueEntryTO { + public String album; + public String albumAsin; + public String artist; + public String asin; + public String cardImageURL; + public String contentId; + public String contentType; + public int durationSeconds; + public boolean feedbackDisabled; + public String historicalId; + public String imageURL; + public int index; + public boolean isAd; + public boolean isDisliked; + public boolean isFreeWithPrime; + public boolean isLiked; + public String programId; + public String programName; + public String providerId; + public String queueId; + public String radioStationCallSign; + public String radioStationId; + public String radioStationLocation; + public String radioStationSlogan; + public String referenceId; + public String service; + public String startTime; + public String title; + public String trackId; + public String trackStatus; + + @Override + public @NonNull String toString() { + return "MediaStateQueueEntryTO{album='" + album + "', albumAsin='" + albumAsin + "', artist='" + artist + + "', asin='" + asin + "', cardImageURL='" + cardImageURL + "', contentId='" + contentId + + "', contentType='" + contentType + "', durationSeconds=" + durationSeconds + ", feedbackDisabled=" + + feedbackDisabled + ", historicalId='" + historicalId + "', imageURL='" + imageURL + "', index=" + + index + ", isAd=" + isAd + ", isDisliked=" + isDisliked + ", isFreeWithPrime=" + isFreeWithPrime + + ", isLiked=" + isLiked + ", programId='" + programId + "', programName='" + programName + + "', providerId='" + providerId + "', queueId='" + queueId + "', radioStationCallSign='" + + radioStationCallSign + "', radioStationId='" + radioStationId + "', radioStationLocation='" + + radioStationLocation + "', radioStationSlogan='" + radioStationSlogan + "', referenceId='" + + referenceId + "', service='" + service + "', startTime='" + startTime + "', title='" + title + + "', trackId='" + trackId + "', trackStatus='" + trackStatus + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateTO.java new file mode 100644 index 0000000000..2493efa94a --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MediaStateTO.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link MediaStateTO} encapsulates the state of a media content + * + * @author Jan N. Klug - Initial contribution + */ +public class MediaStateTO { + + public String clientId; + public String contentId; + public String contentType; + public String currentState; + public String imageURL; + public boolean isDisliked; + public boolean isLiked; + public boolean looping; + public String mediaOwnerCustomerId; + public boolean muted; + public String programId; + public int progressSeconds; + public String providerId; + public List queue = List.of(); + public String queueId; + public int queueSize; + public int radioVariety; + public String referenceId; + public String service; + public boolean shuffling; + public int volume; + + @Override + public @NonNull String toString() { + return "MediaStateTO{clientId='" + clientId + "', contentId='" + contentId + "', contentType='" + contentType + + "', currentState='" + currentState + "', imageURL='" + imageURL + "', isDisliked=" + isDisliked + + ", isLiked=" + isLiked + ", looping=" + looping + ", mediaOwnerCustomerId='" + mediaOwnerCustomerId + + "', muted=" + muted + ", programId='" + programId + "', progressSeconds=" + progressSeconds + + ", providerId='" + providerId + "', queue=" + queue + ", queueId='" + queueId + "', queueSize=" + + queueSize + "', radioVariety=" + radioVariety + ", referenceId='" + referenceId + "', service='" + + service + "', shuffling=" + shuffling + ", volume=" + volume + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderDataTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderDataTO.java new file mode 100644 index 0000000000..4f86bef774 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderDataTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link MusicProviderDataTO} encapsulates music provider metadata + * + * @author Jan N. Klug - Initial contribution + */ +public class MusicProviderDataTO { + public boolean isDefaultMusicProvider; + public boolean isDefaultStationProvider; + + @Override + public @NonNull String toString() { + return "MusicProviderDataTO{isDefaultMusicProvider=" + isDefaultMusicProvider + ", isDefaultStationProvider=" + + isDefaultStationProvider + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderTO.java new file mode 100644 index 0000000000..248e9e5755 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/MusicProviderTO.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +import com.google.gson.reflect.TypeToken; + +/** + * The {@link MusicProviderTO} encapsulate a single music provider + * + * @author Jan N. Klug - Initial contribution + */ +public class MusicProviderTO { + @SuppressWarnings("unchecked") + public static final TypeToken> LIST_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(List.class, MusicProviderTO.class); + + public String id; + public String displayName; + public String description; + public List supportedProperties = List.of(); + public List supportedTriggers = List.of(); + public List supportedOperations = List.of(); + public String availability; + public String icon; + public MusicProviderDataTO providerData = new MusicProviderDataTO(); + + @Override + public @NonNull String toString() { + return "MusicProviderTO{id='" + id + "', displayName='" + displayName + "', description='" + description + + "', supportedProperties=" + supportedProperties + ", supportedTriggers=" + supportedTriggers + + ", supportedOperations=" + supportedOperations + ", availability='" + availability + "', icon='" + + icon + "', providerData=" + providerData + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationListResponseTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationListResponseTO.java new file mode 100644 index 0000000000..615fe4b282 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationListResponseTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationTO; + +/** + * The {@link NotificationListResponseTO} encapsulate response of a /api/notifications + * + * @author Jan N. Klug - Initial contribution + */ +public class NotificationListResponseTO { + public List notifications = List.of(); + + @Override + public @NonNull String toString() { + return "NotificationListResponseTO{notifications=" + notifications + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationSoundResponseTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationSoundResponseTO.java new file mode 100644 index 0000000000..8f93a22124 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/NotificationSoundResponseTO.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; + +/** + * The {@link NotificationSoundResponseTO} encapsulate response of /api/notification/sounds + * + * @author Jan N. Klug - Initial contribution + */ +public class NotificationSoundResponseTO { + public List notificationSounds = List.of(); + + @Override + public @NonNull String toString() { + return "NotificationSoundResponseTO{notificationSounds=" + notificationSounds + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayListTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayListTO.java new file mode 100644 index 0000000000..c0a657a61c --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayListTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayListTO} encapsulates a single playlist + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayListTO { + public String playlistId; + public String title; + public int trackCount; + + @Override + public @NonNull String toString() { + return "PlayListTO{playlistId='" + playlistId + "', title='" + title + "', trackCount=" + trackCount + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayerStateTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayerStateTO.java new file mode 100644 index 0000000000..07ecf9233f --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlayerStateTO.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateInfoTO; + +/** + * The {@link PlayerStateTO} encapsulate the response of a request to /api/np/player + * + * @author Jan N. Klug - Initial contribution + */ +public class PlayerStateTO { + public PlayerStateInfoTO playerInfo = new PlayerStateInfoTO(); + + @Override + public @NonNull String toString() { + return "PlayerStateTO{playerInfo=" + playerInfo + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlaylistsTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlaylistsTO.java new file mode 100644 index 0000000000..6c269645d7 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/PlaylistsTO.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link PlayerStateTO} encapsulates the response of a playlist request + * + * @author Jan N. Klug - Initial contribution + */ +public class PlaylistsTO { + public Map> playlists = Map.of(); + + @Override + public @NonNull String toString() { + return "PlaylistsTO{playlists=" + playlists + "}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/UsersMeTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/UsersMeTO.java new file mode 100644 index 0000000000..25ba96e3a0 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/UsersMeTO.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link UsersMeTO} encapsulate the response of /api/users/me + * + * @author Michael Geramb - Initial contribution + */ +public class UsersMeTO { + public String countryOfResidence; + public String effectiveMarketPlaceId; + public String email; + public Boolean eulaAcceptance; + public List features = List.of(); + public String fullName; + public Boolean hasActiveDopplers; + public String id; + public String marketPlaceDomainName; + public String marketPlaceId; + public String marketPlaceLocale; + + @Override + public @NonNull String toString() { + return "UsersMeTO{countryOfResidence='" + countryOfResidence + "', effectiveMarketPlaceId='" + + effectiveMarketPlaceId + "', email='" + email + "', eulaAcceptance=" + eulaAcceptance + ", features=" + + features + ", fullName='" + fullName + "'" + ", hasActiveDopplers=" + hasActiveDopplers + ", id='" + + id + "', marketPlaceDomainName='" + marketPlaceDomainName + "', marketPlaceId='" + marketPlaceId + + "', marketPlaceLocale='" + marketPlaceLocale + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordTO.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordTO.java new file mode 100644 index 0000000000..6b0b79afc5 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordTO.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.dto.response; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * The {@link WakeWordTO} encapsulates a single wake word definition + * + * @author Jan N. Klug - Initial contribution + */ +public class WakeWordTO { + public boolean active; + public String deviceSerialNumber; + public String deviceType; + public Object midFieldState; + public String wakeWord; + + @Override + public @NonNull String toString() { + return "WakeWordTO{active=" + active + ", deviceSerialNumber='" + deviceSerialNumber + "', deviceType='" + + deviceType + "', midFieldState=" + midFieldState + ", wakeWord='" + wakeWord + "'}"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEnabledFeeds.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordsTO.java similarity index 53% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEnabledFeeds.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordsTO.java index 8019347cd3..64ab3be28f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEnabledFeeds.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/response/WakeWordsTO.java @@ -1,5 +1,4 @@ /** - * Copyright (c) 2010-2021 Contributors to the openHAB project * Copyright (c) 2021-2023 Contributors to the SmartHome/J project * * See the NOTICE file(s) distributed with this work for additional @@ -11,19 +10,22 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.response; import java.util.List; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jdt.annotation.NonNull; /** - * The {@link JsonEnabledFeeds} encapsulate the GSON data of the enabled feeds list + * The {@link WakeWordsTO} encapsulate the response of a request to /api/wake-word * * @author Michael Geramb - Initial contribution */ -@NonNullByDefault -public class JsonEnabledFeeds { - public @Nullable List enabledFeeds; +public class WakeWordsTO { + public List wakeWords = List.of(); + + @Override + public @NonNull String toString() { + return "WakeWords{wakeWords=" + wakeWords + "}"; + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapability.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeCapability.java similarity index 98% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapability.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeCapability.java index f661608ea2..a7470fc15c 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapability.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeCapability.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import java.util.List; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevice.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDevice.java similarity index 92% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevice.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDevice.java index 0a2b121bf3..2bf481e088 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevice.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDevice.java @@ -11,15 +11,15 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import java.util.List; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDeviceNetworkState.SmartHomeDeviceNetworkState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeTags.JsonSmartHomeTag; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDeviceNetworkState.SmartHomeDeviceNetworkState; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeTags.JsonSmartHomeTag; /** * The {@link JsonSmartHomeDevice} encapsulates smarthome device API responses diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceAlias.java similarity index 92% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceAlias.java index c9167b209d..ee9eefd847 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceAlias.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceNetworkState.java similarity index 93% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceNetworkState.java index 5fab6bb31d..8f9a546a13 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeDeviceNetworkState.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentifiers.java similarity index 93% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentifiers.java index e288737802..5472fe7956 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentifiers.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentity.java similarity index 94% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentity.java index 2fd2d96044..d0a80d4a90 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroupIdentity.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import java.util.List; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroups.java similarity index 90% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroups.java index 69b9cf285a..fb8df6d6ca 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeGroups.java @@ -11,13 +11,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentifiers.SmartHomeGroupIdentifier; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroupIdentifiers.SmartHomeGroupIdentifier; /** * @author Lukas Knoeller - Initial contribution diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeTags.java similarity index 81% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeTags.java index fbfcf22e51..f136981ca0 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/JsonSmartHomeTags.java @@ -11,11 +11,11 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentity.SmartHomeGroupIdentity; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroupIdentity.SmartHomeGroupIdentity; /** * @author Lukas Knoeller - Initial contribution diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/SmartHomeBaseDevice.java similarity index 91% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/SmartHomeBaseDevice.java index 782452f9ce..80c32313ff 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/dto/smarthome/SmartHomeBaseDevice.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/AccountHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/AccountHandler.java index 17f77c0ca9..79c1f4adc0 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/AccountHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/AccountHandler.java @@ -13,21 +13,27 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.handler; -import java.io.IOException; -import java.net.URISyntaxException; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_REFRESH_ACTIVITY; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_SEND_MESSAGE; +import static org.smarthomej.binding.amazonechocontrol.internal.dto.TOMapper.map; +import static org.smarthomej.binding.amazonechocontrol.internal.push.PushConnection.State.CLOSED; +import static org.smarthomej.binding.amazonechocontrol.internal.push.PushConnection.State.CONNECTED; +import static org.smarthomej.binding.amazonechocontrol.internal.util.Util.findIn; + import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; import java.time.ZonedDateTime; +import java.time.chrono.ChronoZonedDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledFuture; @@ -37,6 +43,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; import org.openhab.core.storage.Storage; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; @@ -48,48 +57,42 @@ import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.osgi.service.http.HttpService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.AccountHandlerConfig; -import org.smarthomej.binding.amazonechocontrol.internal.AccountServlet; +import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlCommandDescriptionProvider; import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.AmazonHandlerCallback; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.ChannelHandler; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.ChannelHandlerSendMessage; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; import org.smarthomej.binding.amazonechocontrol.internal.discovery.AmazonEchoDiscovery; import org.smarthomej.binding.amazonechocontrol.internal.discovery.SmartHomeDevicesDiscovery; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAscendingAlarm.AscendingAlarmModel; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushActivity; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushActivity.Key; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushDevice.DopplerId; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushNotificationChange; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCustomerHistoryRecords.CustomerHistoryRecord; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState.DeviceNotificationState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDoNotDisturb.DoNotDisturbDeviceStatus; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonFeed; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMusicProvider; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaylists; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPushCommand; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonWakeWords.WakeWord; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.AscendingAlarmModelTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceNotificationStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DoNotDisturbDeviceStatusTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EnabledFeedTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.NotifyNowPlayingUpdatedTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushCommandTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushDeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushDopplerIdTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.SendConversationDTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.AccountTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MusicProviderTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.WakeWordTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroups; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice; +import org.smarthomej.binding.amazonechocontrol.internal.push.PushConnection; +import org.smarthomej.binding.amazonechocontrol.internal.smarthome.JsonNetworkDetails; import org.smarthomej.binding.amazonechocontrol.internal.smarthome.SmartHomeDeviceStateGroupUpdateCalculator; -import org.smarthomej.binding.amazonechocontrol.internal.websocket.WebSocketCommandHandler; -import org.smarthomej.binding.amazonechocontrol.internal.websocket.WebSocketConnection; -import org.smarthomej.binding.amazonechocontrol.internal.websocket.WebsocketException; +import org.smarthomej.binding.amazonechocontrol.internal.types.Notification; import com.google.gson.Gson; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; /** @@ -98,144 +101,137 @@ * @author Michael Geramb - Initial Contribution */ @NonNullByDefault -public class AccountHandler extends BaseBridgeHandler implements WebSocketCommandHandler, AmazonHandlerCallback { +public class AccountHandler extends BaseBridgeHandler implements PushConnection.Listener { + private static final int CHECK_DATA_INTERVAL = 3600; // in seconds (always refresh every hour) + private static final int CHECK_LOGIN_INTERVAL = 60; // in seconds (always check every minute) + private final Logger logger = LoggerFactory.getLogger(AccountHandler.class); - private final Storage stateStorage; - private final HttpClient httpClient; - private @Nullable Connection connection; - private @Nullable WebSocketConnection webSocketConnection; + private final Storage sessionStorage; + private final AmazonEchoControlCommandDescriptionProvider commandDescriptionProvider; + private Connection connection; - private final Set echoHandlers = new CopyOnWriteArraySet<>(); + private final Map echoHandlers = new ConcurrentHashMap<>(); private final Set smartHomeDeviceHandlers = new CopyOnWriteArraySet<>(); private final Set flashBriefingProfileHandlers = new CopyOnWriteArraySet<>(); private final Object synchronizeConnection = new Object(); - private Map jsonSerialNumberDeviceMapping = new HashMap<>(); + private Map serialNumberDeviceMapping = new HashMap<>(); private Map jsonIdSmartHomeDeviceMapping = new HashMap<>(); private @Nullable ScheduledFuture checkDataJob; - private @Nullable ScheduledFuture checkLoginJob; private @Nullable ScheduledFuture updateSmartHomeStateJob; - private @Nullable ScheduledFuture refreshAfterCommandJob; + private @Nullable ScheduledFuture refreshActivityJob; private @Nullable ScheduledFuture refreshSmartHomeAfterCommandJob; private final Object synchronizeSmartHomeJobScheduler = new Object(); - private @Nullable ScheduledFuture forceCheckDataJob; - private String currentFlashBriefingJson = ""; - private final HttpService httpService; - private @Nullable AccountServlet accountServlet; + + private List currentFlashBriefings = List.of(); private final Gson gson; - private int checkDataCounter; + private int lastMessageId = 1000; + private long nextDataRefresh = 0; + private long nextLoginCheck = 0; + private long nextRefreshNotifications = 0; + private final LinkedBlockingQueue requestedDeviceUpdates = new LinkedBlockingQueue<>(); private @Nullable SmartHomeDeviceStateGroupUpdateCalculator smartHomeDeviceStateGroupUpdateCalculator; - private final List channelHandlers = new ArrayList<>(); private AccountHandlerConfig handlerConfig = new AccountHandlerConfig(); + private final PushConnection pushConnection; + private boolean disposing = false; + private @Nullable AccountTO accountInformation; - public AccountHandler(Bridge bridge, HttpService httpService, Storage stateStorage, Gson gson, - HttpClient httpClient) { + public AccountHandler(Bridge bridge, Storage stateStorage, Gson gson, HttpClient httpClient, + HTTP2Client http2Client, AmazonEchoControlCommandDescriptionProvider commandDescriptionProvider) { super(bridge); this.gson = gson; - this.httpClient = httpClient; - this.httpService = httpService; - this.stateStorage = stateStorage; - channelHandlers.add(new ChannelHandlerSendMessage(this, this.gson)); + this.sessionStorage = stateStorage; + this.pushConnection = new PushConnection(http2Client, gson, this, scheduler); + this.commandDescriptionProvider = commandDescriptionProvider; + this.connection = new Connection(null, gson, httpClient); } @Override public void initialize() { + disposing = false; handlerConfig = getConfig().as(AccountHandlerConfig.class); - synchronized (synchronizeConnection) { - Connection connection = this.connection; - if (connection == null) { - this.connection = new Connection(null, gson); - } - } + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Wait for login"); - if (accountServlet == null) { - try { - accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this, gson); - } catch (IllegalStateException e) { - logger.warn("Failed to create account servlet", e); - } - } + nextDataRefresh = 0; + nextLoginCheck = 0; - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Wait for login"); + checkDataJob = scheduler.scheduleWithFixedDelay(this::checkLoginAndData, 0, 1, TimeUnit.SECONDS); - checkLoginJob = scheduler.scheduleWithFixedDelay(this::checkLogin, 0, 60, TimeUnit.SECONDS); - checkDataJob = scheduler.scheduleWithFixedDelay(this::checkData, 4, 60, TimeUnit.SECONDS); + int pollingIntervalAlexa = Math.min(handlerConfig.pollingIntervalSmartHomeAlexa, 10); + int pollingIntervalSkills = Math.min(handlerConfig.pollingIntervalSmartSkills, 60); - int pollingIntervalAlexa = handlerConfig.pollingIntervalSmartHomeAlexa; - if (pollingIntervalAlexa < 10) { - pollingIntervalAlexa = 10; - } - int pollingIntervalSkills = handlerConfig.pollingIntervalSmartSkills; - if (pollingIntervalSkills < 60) { - pollingIntervalSkills = 60; - } smartHomeDeviceStateGroupUpdateCalculator = new SmartHomeDeviceStateGroupUpdateCalculator(pollingIntervalAlexa, pollingIntervalSkills); updateSmartHomeStateJob = scheduler.scheduleWithFixedDelay(() -> updateSmartHomeState(null), 20, 10, TimeUnit.SECONDS); } - @Override - public void updateChannelState(String channelId, State state) { - updateState(channelId, state); - } - @Override public void handleCommand(ChannelUID channelUID, Command command) { try { logger.trace("Command '{}' received for channel '{}'", command, channelUID); - Connection connection = this.connection; - if (connection == null) { + if (!connection.isLoggedIn()) { + logger.info("Can't handle commands when account is logged out."); return; } - String channelId = channelUID.getId(); - for (ChannelHandler channelHandler : channelHandlers) { - if (channelHandler.tryHandleCommand(new Device(), connection, channelId, command)) { + if (channelId.equals(CHANNEL_REFRESH_ACTIVITY) && command instanceof OnOffType) { + for (CustomerHistoryRecordTO record : getCustomerActivity(null)) { + String[] keyParts = record.recordKey.split("#"); + String serialNumber = keyParts[keyParts.length - 1]; + EchoHandler echoHandler = echoHandlers.get(serialNumber); + if (echoHandler != null) { + echoHandler.handlePushActivity(record); + } + } + } else if (channelId.equals(CHANNEL_SEND_MESSAGE) && command instanceof StringType) { + String commandValue = command.toFullString(); + String baseUrl = "https://alexa-comms-mobile-service." + connection.getRetailDomain(); + + AccountTO currentAccount = this.accountInformation; + if (currentAccount == null) { + String accountResult = connection.getRequestBuilder().get(baseUrl + "/accounts") + .syncSend(String.class); + List accounts = gson.fromJson(accountResult, AccountTO.LIST_TYPE_TOKEN); + currentAccount = accounts.stream().filter(a -> a.signedInUser).findFirst().orElse(null); + this.accountInformation = currentAccount; + } + + if (currentAccount == null || currentAccount.commsId == null) { return; } - } - if (command instanceof RefreshType) { - refreshData(); + + SendConversationDTO conversation = new SendConversationDTO(); + conversation.conversationId = "amzn1.comms.messaging.id.conversationV2~31e6fe8f-8b0c-4e84-a1e4-80030a09009b"; + conversation.clientMessageId = java.util.UUID.randomUUID().toString(); + conversation.messageId = lastMessageId++; + conversation.sender = currentAccount.commsId; + conversation.time = LocalDateTime.now().toString(); + conversation.payload.put("text", commandValue); + + String sendUrl = baseUrl + "/users/" + currentAccount.commsId + "/conversations/" + + currentAccount.commsId + "/messages"; + connection.getRequestBuilder().post(sendUrl).withContent(List.of(conversation)).syncSend(); } } catch (ConnectionException e) { logger.info("handleCommand fails", e); } } - @Override - public void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title, - @Nullable Integer volume) throws IOException, URISyntaxException { - EchoHandler echoHandler = findEchoHandlerBySerialNumber(device.serialNumber); - if (echoHandler != null) { - echoHandler.startAnnouncement(device, speak, bodyText, title, volume); - } - } - public Set getFlashBriefingProfileHandlers() { - return new HashSet<>(flashBriefingProfileHandlers); + return Set.copyOf(flashBriefingProfileHandlers); } - public List getLastKnownDevices() { - return new ArrayList<>(jsonSerialNumberDeviceMapping.values()); + public List getLastKnownDevices() { + return List.copyOf(serialNumberDeviceMapping.values()); } public List getLastKnownSmartHomeDevices() { - return new ArrayList<>(jsonIdSmartHomeDeviceMapping.values()); - } - - public void addEchoHandler(EchoHandler echoHandler) { - if (echoHandlers.add(echoHandler)) { - forceCheckData(); - } - } - - public void removeEchoHandler(EchoHandler echoHandler) { - echoHandlers.remove(echoHandler); + return List.copyOf(jsonIdSmartHomeDeviceMapping.values()); } public void addSmartHomeDeviceHandler(SmartHomeDeviceHandler smartHomeDeviceHandler) { @@ -244,54 +240,16 @@ public void addSmartHomeDeviceHandler(SmartHomeDeviceHandler smartHomeDeviceHand } } - public void removeSmartHomeDeviceHandler(SmartHomeDeviceHandler smartHomeDeviceHandler) { - smartHomeDeviceHandlers.remove(smartHomeDeviceHandler); - } - public void forceCheckData() { - if (forceCheckDataJob == null) { - forceCheckDataJob = scheduler.schedule(this::checkData, 1000, TimeUnit.MILLISECONDS); - } + nextDataRefresh = 0; } - public @Nullable Thing findThingBySerialNumber(@Nullable String deviceSerialNumber) { - EchoHandler echoHandler = findEchoHandlerBySerialNumber(deviceSerialNumber); - if (echoHandler != null) { - return echoHandler.getThing(); - } - return null; - } - - public @Nullable EchoHandler findEchoHandlerBySerialNumber(@Nullable String deviceSerialNumber) { - for (EchoHandler echoHandler : echoHandlers) { - if (deviceSerialNumber != null && deviceSerialNumber.equals(echoHandler.findSerialNumber())) { - return echoHandler; - } - } - return null; - } - - public void addFlashBriefingProfileHandler(FlashBriefingProfileHandler flashBriefingProfileHandler) { - flashBriefingProfileHandlers.add(flashBriefingProfileHandler); - Connection connection = this.connection; - if (connection != null && connection.getIsLoggedIn()) { - if (currentFlashBriefingJson.isEmpty()) { - updateFlashBriefingProfiles(connection); - } - flashBriefingProfileHandler.initialize(this, currentFlashBriefingJson); + public @Nullable Thing getThingBySerialNumber(@Nullable String deviceSerialNumber) { + if (deviceSerialNumber == null) { + return null; } - // set flashbriefing description on echo handlers - echoHandlers.forEach(h -> h.createStartCommandCommandOptions(flashBriefingProfileHandlers)); - } - - private void scheduleUpdate() { - checkDataCounter = 999; - } - - @Override - public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { - super.childHandlerInitialized(childHandler, childThing); - scheduleUpdate(); + EchoHandler echoHandler = echoHandlers.get(deviceSerialNumber); + return echoHandler == null ? null : echoHandler.getThing(); } @Override @@ -300,33 +258,41 @@ public void handleRemoval() { super.handleRemoval(); } + @Override + public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { + if (childHandler instanceof EchoHandler echoHandler) { + echoHandlers.put(echoHandler.getSerialNumber(), echoHandler); + forceCheckData(); + return; + } else if (childHandler instanceof FlashBriefingProfileHandler flashBriefingProfileHandler) { + flashBriefingProfileHandlers.add(flashBriefingProfileHandler); + if (currentFlashBriefings.isEmpty()) { + currentFlashBriefings = updateFlashBriefingProfiles(); + flashBriefingProfileHandler.updateAndCheck(currentFlashBriefings); + } + // set flash-briefing description on echo handlers + commandDescriptionProvider.setEchoHandlerStartCommands(echoHandlers.values(), flashBriefingProfileHandlers); + } + nextDataRefresh = Math.min(nextDataRefresh, System.currentTimeMillis() + 60L * 1000); // refresh latest within + // one minute + } + @Override public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { - // check for echo handler if (childHandler instanceof EchoHandler) { - echoHandlers.remove(childHandler); - } - // check for flash briefing profile handler - if (childHandler instanceof FlashBriefingProfileHandler) { + echoHandlers.values().remove(childHandler); + } else if (childHandler instanceof FlashBriefingProfileHandler) { flashBriefingProfileHandlers.remove(childHandler); - echoHandlers.forEach(h -> h.createStartCommandCommandOptions(flashBriefingProfileHandlers)); - } - // check for flash briefing profile handler - if (childHandler instanceof SmartHomeDeviceHandler) { + commandDescriptionProvider.setEchoHandlerStartCommands(echoHandlers.values(), flashBriefingProfileHandlers); + } else if (childHandler instanceof SmartHomeDeviceHandler) { smartHomeDeviceHandlers.remove(childHandler); } - super.childHandlerDisposed(childHandler, childThing); } @Override public void dispose() { - AccountServlet accountServlet = this.accountServlet; - if (accountServlet != null) { - accountServlet.dispose(); - } - this.accountServlet = null; + disposing = true; cleanup(); - super.dispose(); } private void cleanup() { @@ -341,32 +307,13 @@ private void cleanup() { refreshJob.cancel(true); this.checkDataJob = null; } - ScheduledFuture refreshLogin = this.checkLoginJob; - if (refreshLogin != null) { - refreshLogin.cancel(true); - this.checkLoginJob = null; - } - ScheduledFuture foceCheckDataJob = this.forceCheckDataJob; - if (foceCheckDataJob != null) { - foceCheckDataJob.cancel(true); - this.forceCheckDataJob = null; - } - ScheduledFuture refreshAfterCommandJob = this.refreshAfterCommandJob; - if (refreshAfterCommandJob != null) { - refreshAfterCommandJob.cancel(true); - this.refreshAfterCommandJob = null; - } ScheduledFuture refreshSmartHomeAfterCommandJob = this.refreshSmartHomeAfterCommandJob; if (refreshSmartHomeAfterCommandJob != null) { refreshSmartHomeAfterCommandJob.cancel(true); this.refreshSmartHomeAfterCommandJob = null; } - Connection connection = this.connection; - if (connection != null) { - connection.logout(); - this.connection = null; - } - closeWebSocketConnection(); + pushConnection.close(); + connection.logout(false); } private void checkLogin() { @@ -375,454 +322,354 @@ private void checkLogin() { logger.debug("check login {}", uid.getAsString()); synchronized (synchronizeConnection) { - Connection currentConnection = this.connection; - if (currentConnection == null) { - return; - } - try { - if (currentConnection.getIsLoggedIn()) { - if (currentConnection.checkRenewSession()) { - setConnection(currentConnection); + if (connection.isLoggedIn()) { + if (connection.renewTokens()) { + storeSession(); } } else { // read session data from property - String sessionStore = this.stateStorage.get("sessionStorage"); + String sessionStore = sessionStorage.get("sessionStorage"); // try to use the session data - if (currentConnection.tryRestoreLogin(sessionStore, null)) { - setConnection(currentConnection); + if (connection.restoreLogin(sessionStore, null)) { + storeSession(); + nextDataRefresh = 0; } } - if (!currentConnection.getIsLoggedIn()) { + if (!connection.isLoggedIn()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "Please login in through web site: http(s)://:/amazonechocontrol/" + "Please login in through servlet: http(s)://:/amazonechocontrol/" + URLEncoder.encode(uid.getId(), StandardCharsets.UTF_8)); + if (pushConnection.getState() != CLOSED) { + // close push connection if we are not logged in + pushConnection.close(); + } + } else { + updateStatus(ThingStatus.ONLINE); + if (pushConnection.getState() == CLOSED) { + pushConnection.open(connection.getRetailDomain(), connection.getAccessToken()); + } else if (pushConnection.getState() == CONNECTED) { + // if the push connection is already logged in, check if it is alive + pushConnection.sendPing(); + } } } catch (ConnectionException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - } catch (URISyntaxException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); } } - } catch (Exception e) { // this handler can be removed later, if we know that nothing else can fail. + } catch (RuntimeException e) { // this handler can be removed later, if we know that nothing else can fail. logger.error("check login fails with unexpected error", e); } } - // used to set a valid connection from the web proxy login - public void setConnection(@Nullable Connection connection) { - this.connection = connection; - if (connection != null) { - String serializedStorage = connection.getLoginData().serializeLoginData(); - this.stateStorage.put("sessionStorage", serializedStorage); - } else { - this.stateStorage.put("sessionStorage", null); - updateStatus(ThingStatus.OFFLINE); - } - closeWebSocketConnection(); - if (connection != null) { - updateDeviceList(); - updateSmartHomeDeviceList(false); - updateFlashBriefingHandlers(); - updateStatus(ThingStatus.ONLINE); - scheduleUpdate(); - checkData(); - } + public void resetConnection(boolean newDevice) { + pushConnection.close(); + connection.logout(newDevice); + sessionStorage.put("sessionStorage", null); + + updateStatus(ThingStatus.OFFLINE); } - void closeWebSocketConnection() { - WebSocketConnection webSocketConnection = this.webSocketConnection; - this.webSocketConnection = null; - if (webSocketConnection != null) { - webSocketConnection.close(); - } + // used to set a valid connection from the web proxy login + public void setConnection(Connection newConnection) { + pushConnection.close(); + connection = newConnection; + storeSession(); + + // force data check + nextLoginCheck = 0; + nextDataRefresh = 0; } - private boolean checkWebSocketConnection() { - WebSocketConnection webSocketConnection = this.webSocketConnection; - if (webSocketConnection == null || webSocketConnection.isClosed()) { - Connection connection = this.connection; - if (connection != null && connection.getIsLoggedIn()) { - try { - this.webSocketConnection = new WebSocketConnection(connection, this, gson, httpClient); - } catch (WebsocketException e) { - if (e.getCause() != null) { - logger.warn("{}", e.getMessage(), e); - } else { - logger.warn("{}", e.getMessage()); - } - } - } - return false; - } - return true; + private void storeSession() { + String serializedStorage = connection.getLoginData().serializeLoginData(); + sessionStorage.put("sessionStorage", serializedStorage); } - private void checkData() { + private void checkLoginAndData() { + long now = System.currentTimeMillis(); synchronized (synchronizeConnection) { try { - Connection connection = this.connection; - if (connection != null && connection.getIsLoggedIn()) { - checkDataCounter++; - if (checkDataCounter > 60 || forceCheckDataJob != null) { - checkDataCounter = 0; - forceCheckDataJob = null; - } - if (!checkWebSocketConnection() || checkDataCounter == 0) { + if (now > nextLoginCheck) { + nextLoginCheck = now + CHECK_LOGIN_INTERVAL * 1000; + checkLogin(); + } + if (connection.isLoggedIn()) { + if (now > nextDataRefresh) { + nextDataRefresh = now + CHECK_DATA_INTERVAL * 1000; refreshData(); } + if (now > nextRefreshNotifications) { + refreshNotifications(); + } } - logger.debug("checkData {} finished", getThing().getUID().getAsString()); - } catch (JsonSyntaxException e) { - logger.debug("checkData fails", e); - } catch (Exception e) { // this handler can be removed later, if we know that nothing else can fail. - logger.error("checkData fails with unexpected error", e); + } catch (RuntimeException e) { // this handler can be removed later, if we know that nothing else can fail. + logger.warn("checkData fails with unexpected error", e); } } } - private void refreshNotifications(@Nullable JsonCommandPayloadPushNotificationChange pushPayload) { - Connection currentConnection = this.connection; - if (currentConnection == null) { - return; - } - if (!currentConnection.getIsLoggedIn()) { + private void refreshNotifications() { + if (!connection.isLoggedIn()) { return; } - ZonedDateTime timeStamp = ZonedDateTime.now(); - try { - List notifications = currentConnection.notifications(); - ZonedDateTime timeStampNow = ZonedDateTime.now(); - echoHandlers.forEach(echoHandler -> echoHandler.updateNotifications(timeStamp, timeStampNow, pushPayload, - notifications)); - } catch (ConnectionException e) { - logger.debug("refreshNotifications failed", e); + ZonedDateTime requestTime = ZonedDateTime.now(); + List notifications = connection.getNotifications().stream() + .map(n -> map(n, requestTime, ZonedDateTime.now())).filter(Objects::nonNull) + .map(Objects::requireNonNull).toList(); + echoHandlers.values().forEach(echoHandler -> echoHandler.updateNotifications(notifications)); + ZonedDateTime first = notifications.stream().map(Notification::nextAlarmTime) + .min(ChronoZonedDateTime::compareTo).orElse(null); + if (first != null) { + nextRefreshNotifications = first.toEpochSecond() * 1000; + } else { + nextRefreshNotifications = Long.MAX_VALUE; } } private void refreshData() { - synchronized (synchronizeConnection) { - try { - logger.debug("refreshing data {}", getThing().getUID().getAsString()); + try { + logger.debug("refreshing data {}", getThing().getUID().getAsString()); - // check if logged in - Connection currentConnection = connection; - if (currentConnection != null) { - if (!currentConnection.getIsLoggedIn()) { - return; - } - } - if (currentConnection == null) { + // check if logged in + if (!connection.isLoggedIn()) { + return; + } + + // get all devices registered in the account + updateDeviceList(); + updateSmartHomeDeviceList(false); + updateFlashBriefingHandlers(); + + List deviceNotificationStates = connection.getDeviceNotificationStates(); + List ascendingAlarmModels = connection.getAscendingAlarms(); + List doNotDisturbDeviceStatuses = connection.getDoNotDisturbs(); + List bluetoothStates = connection.getBluetoothConnectionStates(); + List musicProviders = connection.getMusicProviders(); + + // forward device information to echo handler + echoHandlers.forEach((serialNumber, echoHandler) -> { + DeviceTO device = serialNumberDeviceMapping.get(serialNumber); + if (device == null) { return; } - // get all devices registered in the account - updateDeviceList(); - updateSmartHomeDeviceList(false); - updateFlashBriefingHandlers(); - - List deviceNotificationStates = List.of(); - List ascendingAlarmModels = List.of(); - List doNotDisturbDeviceStatuses = List.of(); - JsonBluetoothStates states = null; - List musicProviders = null; - if (currentConnection.getIsLoggedIn()) { - // update notification states - deviceNotificationStates = currentConnection.getDeviceNotificationStates(); - - // update ascending alarm - ascendingAlarmModels = currentConnection.getAscendingAlarm(); - - // update do not disturb - doNotDisturbDeviceStatuses = currentConnection.getDoNotDisturb(); - - // update bluetooth states - states = currentConnection.getBluetoothConnectionStates(); - - // update music providers - if (currentConnection.getIsLoggedIn()) { - try { - musicProviders = currentConnection.getMusicProviders(); - } catch (JsonSyntaxException e) { - logger.debug("Update music provider failed", e); - } - } - } - // forward device information to echo handler - for (EchoHandler child : echoHandlers) { - Device device = findDeviceJson(child.findSerialNumber()); - - List notificationSounds = List.of(); - JsonPlaylists playlists = null; - if (device != null && currentConnection.getIsLoggedIn()) { - // update notification sounds - try { - notificationSounds = currentConnection.getNotificationSounds(device); - } catch (JsonSyntaxException | ConnectionException e) { - logger.debug("Update notification sounds failed", e); - } - // update playlists - try { - playlists = currentConnection.getPlaylists(device); - } catch (JsonSyntaxException | ConnectionException e) { - logger.debug("Update playlist failed", e); - } - } + // update alarm sounds + List notificationSounds = connection.getNotificationSounds(device); + commandDescriptionProvider.setEchoHandlerAlarmSounds(echoHandler, notificationSounds); - BluetoothState state = null; - if (states != null) { - state = states.findStateByDevice(device); - } - DeviceNotificationState deviceNotificationState = null; - AscendingAlarmModel ascendingAlarmModel = null; - DoNotDisturbDeviceStatus doNotDisturbDeviceStatus = null; - if (device != null) { - final String serialNumber = device.serialNumber; - if (serialNumber != null) { - ascendingAlarmModel = ascendingAlarmModels.stream() - .filter(current -> serialNumber.equals(current.deviceSerialNumber)).findFirst() - .orElse(null); - deviceNotificationState = deviceNotificationStates.stream() - .filter(current -> serialNumber.equals(current.deviceSerialNumber)).findFirst() - .orElse(null); - doNotDisturbDeviceStatus = doNotDisturbDeviceStatuses.stream() - .filter(current -> serialNumber.equals(current.deviceSerialNumber)).findFirst() - .orElse(null); - } - } - child.updateState(this, device, state, deviceNotificationState, ascendingAlarmModel, - doNotDisturbDeviceStatus, playlists, notificationSounds, musicProviders); - } + BluetoothStateTO bluetoothState = findIn(bluetoothStates, k -> k.deviceSerialNumber, + device.serialNumber).orElse(null); + AscendingAlarmModelTO ascendingAlarmModel = findIn(ascendingAlarmModels, a -> a.deviceSerialNumber, + device.serialNumber).orElse(null); + DeviceNotificationStateTO deviceNotificationState = findIn(deviceNotificationStates, + a -> a.deviceSerialNumber, device.serialNumber).orElse(null); + DoNotDisturbDeviceStatusTO doNotDisturbDeviceStatus = findIn(doNotDisturbDeviceStatuses, + a -> a.deviceSerialNumber, device.serialNumber).orElse(null); - // refresh notifications - refreshNotifications(null); + echoHandler.updateState(device, bluetoothState, deviceNotificationState, ascendingAlarmModel, + doNotDisturbDeviceStatus, musicProviders); + }); - // update account state - updateStatus(ThingStatus.ONLINE); + // refresh notifications + refreshNotifications(); - logger.debug("refresh data {} finished", getThing().getUID().getAsString()); - } catch (JsonSyntaxException e) { - logger.debug("refresh data fails", e); - } catch (Exception e) { // this handler can be removed later, if we know that nothing else can fail. - logger.error("refresh data fails with unexpected error", e); - } + // update account state + updateStatus(ThingStatus.ONLINE); + + logger.debug("refresh data {} finished", getThing().getUID().getAsString()); + } catch (JsonSyntaxException e) { + logger.debug("refresh data fails", e); + } catch (RuntimeException e) { // this handler can be removed later, if we know that nothing else can fail. + logger.error("refresh data fails with unexpected error", e); } } - public @Nullable Device findDeviceJson(@Nullable String serialNumber) { + public @Nullable DeviceTO findDevice(@Nullable String serialNumber) { if (serialNumber == null || serialNumber.isEmpty()) { return null; } - return this.jsonSerialNumberDeviceMapping.get(serialNumber); + return serialNumberDeviceMapping.get(serialNumber); } - public @Nullable Device findDeviceJsonBySerialOrName(@Nullable String serialOrName) { + public @Nullable DeviceTO findDeviceBySerialOrName(@Nullable String serialOrName) { if (serialOrName == null || serialOrName.isEmpty()) { return null; } - return this.jsonSerialNumberDeviceMapping.values().stream().filter( + return this.serialNumberDeviceMapping.values().stream().filter( d -> serialOrName.equalsIgnoreCase(d.serialNumber) || serialOrName.equalsIgnoreCase(d.accountName)) .findFirst().orElse(null); } - public List updateDeviceList() { - Connection currentConnection = connection; - if (currentConnection == null) { - return new ArrayList<>(); + public List updateDeviceList() { + if (!connection.isLoggedIn()) { + return List.of(); } - List devices = null; try { - if (currentConnection.getIsLoggedIn()) { - devices = currentConnection.getDeviceList(); - } - } catch (ConnectionException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); - } - if (devices != null) { - // create new device map - jsonSerialNumberDeviceMapping = devices.stream().filter(device -> device.serialNumber != null) - .collect(Collectors.toMap(d -> Objects.requireNonNull(d.serialNumber), d -> d)); - // notify flashbriefing profile handlers of changed device list - flashBriefingProfileHandlers.forEach(h -> h.setCommandDescription(jsonSerialNumberDeviceMapping.values())); - } + List devices = connection.getDeviceList(); + List wakeWords = connection.getWakeWords(); - List wakeWords = currentConnection.getWakeWords(); - // update handlers - for (EchoHandler echoHandler : echoHandlers) { - String serialNumber = echoHandler.findSerialNumber(); - String deviceWakeWord = wakeWords.stream() - .filter(wakeWord -> serialNumber.equals(wakeWord.deviceSerialNumber)).findFirst() - .map(wakeWord -> wakeWord.wakeWord).orElse(null); - echoHandler.setDeviceAndUpdateThingState(this, findDeviceJson(serialNumber), deviceWakeWord); - } + // create new device map + serialNumberDeviceMapping = devices.stream().collect(Collectors.toMap(d -> d.serialNumber, d -> d)); + // notify flash briefing profile handlers of changed device list + commandDescriptionProvider.setFlashBriefingTargets(flashBriefingProfileHandlers, + serialNumberDeviceMapping.values()); + commandDescriptionProvider.setEchoHandlerStartCommands(echoHandlers.values(), flashBriefingProfileHandlers); + + echoHandlers.forEach((serialNumber, echoHandler) -> { + DeviceTO device = serialNumberDeviceMapping.get(serialNumber); + if (device != null) { + String deviceWakeWord = findIn(wakeWords, w -> w.deviceSerialNumber, serialNumber) + .map(wakeWord -> wakeWord.wakeWord).orElse(null); + echoHandler.setDeviceAndUpdateThingStatus(device, deviceWakeWord); + } + }); - if (devices != null) { return devices; + } catch (ConnectionException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); } + return List.of(); } - public void setEnabledFlashBriefingsJson(String flashBriefingJson) { - Connection currentConnection = connection; - JsonFeed[] feeds = gson.fromJson(flashBriefingJson, JsonFeed[].class); - if (currentConnection != null && feeds != null) { + public void setEnabledFlashBriefing(List flashBriefingConfiguration) { + if (connection.isLoggedIn()) { try { - currentConnection.setEnabledFlashBriefings(Arrays.asList(feeds)); + connection.setEnabledFlashBriefings(flashBriefingConfiguration); } catch (ConnectionException e) { - logger.warn("Set flashbriefing profile failed", e); + logger.warn("Set flash-briefing profile failed", e); } } updateFlashBriefingHandlers(); } - public String getNewCurrentFlashbriefingConfiguration() { - return updateFlashBriefingHandlers(); - } - - public String updateFlashBriefingHandlers() { - Connection currentConnection = connection; - if (currentConnection != null) { - return updateFlashBriefingHandlers(currentConnection); - } - return ""; - } - - private String updateFlashBriefingHandlers(Connection currentConnection) { - if (!flashBriefingProfileHandlers.isEmpty() || currentFlashBriefingJson.isEmpty()) { - updateFlashBriefingProfiles(currentConnection); - } - boolean flashBriefingProfileFound = false; - for (FlashBriefingProfileHandler child : flashBriefingProfileHandlers) { - flashBriefingProfileFound |= child.initialize(this, currentFlashBriefingJson); - } - if (flashBriefingProfileFound) { - return ""; + public List updateFlashBriefingHandlers() { + List currentConfiguration = getEnabledFlashBriefings(); + if (!currentConfiguration.isEmpty()) { + boolean match = false; + for (FlashBriefingProfileHandler handler : flashBriefingProfileHandlers) { + match |= handler.updateAndCheck(currentConfiguration); + } + if (!match) { + // there is no handler associated with the current configuration + return currentConfiguration; + } } - return this.currentFlashBriefingJson; + return List.of(); } - public @Nullable Connection findConnection() { + public Connection getConnection() { return this.connection; } - public String getEnabledFlashBriefingsJson() { - Connection currentConnection = this.connection; - if (currentConnection == null) { - return ""; + public List getEnabledFlashBriefings() { + if (!currentFlashBriefings.isEmpty()) { + return currentFlashBriefings; } - updateFlashBriefingProfiles(currentConnection); - return this.currentFlashBriefingJson; + currentFlashBriefings = updateFlashBriefingProfiles(); + return currentFlashBriefings; } - private void updateFlashBriefingProfiles(Connection currentConnection) { - try { - // Make a copy and remove changeable parts - JsonFeed[] forSerializer = currentConnection.getEnabledFlashBriefings().stream() - .map(source -> new JsonFeed(source.feedId, source.skillId)).toArray(JsonFeed[]::new); - this.currentFlashBriefingJson = gson.toJson(forSerializer); - } catch (JsonSyntaxException | ConnectionException e) { - logger.warn("get flash briefing profiles fails", e); - } + private List updateFlashBriefingProfiles() { + return connection.isLoggedIn() ? connection.getEnabledFlashBriefings().stream().map(this::copyFeed).toList() + : List.of(); } - @Override - public void webSocketCommandReceived(JsonPushCommand pushCommand) { - try { - handleWebsocketCommand(pushCommand); - } catch (Exception e) { - // should never happen, but if the exception is going out of this function, the binding stop working. - logger.warn("handling of websockets fails", e); - } - } - - void handleWebsocketCommand(JsonPushCommand pushCommand) { - String command = pushCommand.command; - if (command != null) { - ScheduledFuture refreshDataDelayed = this.refreshAfterCommandJob; - switch (command) { - case "PUSH_ACTIVITY": - handlePushActivity(pushCommand.payload); - break; - case "PUSH_DOPPLER_CONNECTION_CHANGE": - case "PUSH_BLUETOOTH_STATE_CHANGE": - if (refreshDataDelayed != null) { - refreshDataDelayed.cancel(false); - } - this.refreshAfterCommandJob = scheduler.schedule(this::refreshAfterCommand, 700, - TimeUnit.MILLISECONDS); - break; - case "PUSH_NOTIFICATION_CHANGE": - JsonCommandPayloadPushNotificationChange pushPayload = gson.fromJson(pushCommand.payload, - JsonCommandPayloadPushNotificationChange.class); - refreshNotifications(pushPayload); - break; - default: - String payload = pushCommand.payload; - if (payload != null && payload.startsWith("{") && payload.endsWith("}")) { - JsonCommandPayloadPushDevice devicePayload = Objects - .requireNonNull(gson.fromJson(payload, JsonCommandPayloadPushDevice.class)); - DopplerId dopplerId = devicePayload.dopplerId; - if (dopplerId != null) { - handlePushDeviceCommand(dopplerId, command, payload); - } - } - break; - } - } + private EnabledFeedTO copyFeed(EnabledFeedTO feed) { + EnabledFeedTO newFeed = new EnabledFeedTO(); + newFeed.feedId = feed.feedId; + newFeed.skillId = feed.skillId; + return newFeed; } - private void handlePushDeviceCommand(DopplerId dopplerId, String command, String payload) { - EchoHandler echoHandler = findEchoHandlerBySerialNumber(dopplerId.deviceSerialNumber); - if (echoHandler != null) { - echoHandler.handlePushCommand(command, payload); - } - } - - private void handlePushActivity(@Nullable String payload) { - if (payload == null) { - return; - } - JsonCommandPayloadPushActivity pushActivity = Objects - .requireNonNull(gson.fromJson(payload, JsonCommandPayloadPushActivity.class)); - - Key key = pushActivity.key; - if (key == null) { - return; - } - - Connection connection = this.connection; - if (connection == null || !connection.getIsLoggedIn()) { - return; - } - - Long timestamp = pushActivity.timestamp; - if (timestamp != null) { - long startTimestamp = timestamp - 30000; - long endTimestamp = timestamp + 30000; - List customerHistoryRecords = connection.getActivities(startTimestamp, endTimestamp); - for (CustomerHistoryRecord customerHistoryRecord : customerHistoryRecords) { - String recordKey = customerHistoryRecord.recordKey; - String search = key.registeredUserId + "#" + key.entryId; - if (recordKey != null && search.equals(recordKey)) { - String[] splitRecordKey = recordKey.split("#"); - if (splitRecordKey.length >= 2) { - EchoHandler echoHandler = findEchoHandlerBySerialNumber(splitRecordKey[3]); - if (echoHandler != null) { - echoHandler.handlePushActivity(customerHistoryRecord); - break; + @Override + public void onPushCommandReceived(PushCommandTO pushCommand) { + logger.debug("Processing {}", pushCommand); + String payload = pushCommand.payload; + switch (pushCommand.command) { + case "PUSH_ACTIVITY": + // currently unused, seems to be removed, log a warning if it re-appears + logger.warn("Activity detected: {}", pushCommand); + break; + case "PUSH_DOPPLER_CONNECTION_CHANGE": + case "PUSH_BLUETOOTH_STATE_CHANGE": + long now = System.currentTimeMillis(); + if (nextDataRefresh > now + 1000) { + nextDataRefresh = now + 1000; + } + break; + + case "PUSH_NOTIFICATION_CHANGE": + refreshNotifications(); + break; + case "PUSH_AUDIO_PLAYER_STATE": + case "PUSH_MEDIA_QUEUE_CHANGE": + case "PUSH_MEDIA_CHANGE": + case "PUSH_MEDIA_PROGRESS_CHANGE": + case "PUSH_VOLUME_CHANGE": + case "PUSH_CONTENT_FOCUS_CHANGE": + case "PUSH_EQUALIZER_STATE_CHANGE": + if (payload.startsWith("{") && payload.endsWith("}")) { + PushDeviceTO devicePayload = Objects.requireNonNull(gson.fromJson(payload, PushDeviceTO.class)); + PushDopplerIdTO dopplerId = devicePayload.dopplerId; + if (dopplerId != null) { + EchoHandler echoHandler = echoHandlers.get(dopplerId.deviceSerialNumber); + if (echoHandler == null) { + return; + } + echoHandler.handlePushCommand(pushCommand.command, payload); + if ("PUSH_EQUALIZER_STATE_CHANGE".equals(pushCommand.command)) { + ScheduledFuture refreshActivityJob = this.refreshActivityJob; + if (refreshActivityJob != null) { + refreshActivityJob.cancel(false); + } + this.refreshActivityJob = scheduler.schedule( + () -> handlePushActivity(dopplerId.deviceSerialNumber, pushCommand.timeStamp), + handlerConfig.activityRequestDelay, TimeUnit.SECONDS); } } } - } + break; + case "NotifyNowPlayingUpdated": + NotifyNowPlayingUpdatedTO update = Objects + .requireNonNull(gson.fromJson(payload, NotifyNowPlayingUpdatedTO.class)); + echoHandlers.values().forEach(e -> e.handleNowPlayingUpdated(update.update.update.nowPlayingData)); + break; + case "NotifyMediaSessionsUpdated": + // we can't determine which session was updated, but it only makes sense for currently playing devices + // echoHandlers.forEach(e -> e.refreshAudioPlayerState(true)); + echoHandlers.values().forEach(EchoHandler::updateMediaSessions); + break; + default: + logger.warn("Detected unknown command from activity stream: {}", pushCommand); + } + } + + private List getCustomerActivity(@Nullable Long timestamp) { + if (!connection.isLoggedIn()) { + return List.of(); + } + long realTimestamp = Objects.requireNonNullElse(timestamp, System.currentTimeMillis()); + long startTimestamp = realTimestamp - 120000; + long endTimestamp = realTimestamp + 30000; + + return connection.getActivities(startTimestamp, endTimestamp); + } + + private void handlePushActivity(String deviceSerialNumber, @Nullable Long timestamp) { + List activityRecords = getCustomerActivity(timestamp); + EchoHandler echoHandler = echoHandlers.get(deviceSerialNumber); + if (echoHandler == null) { + logger.warn("Could not find thing handler for serialnumber {}", deviceSerialNumber); + return; } - } - - void refreshAfterCommand() { - refreshData(); + activityRecords.stream().filter(r -> r.recordKey.endsWith(deviceSerialNumber)) + .forEach(echoHandler::handlePushActivity); } private @Nullable SmartHomeBaseDevice findSmartHomeDeviceJson(SmartHomeDeviceHandler handler) { @@ -838,39 +685,73 @@ public int getSmartHomeDevicesDiscoveryMode() { } public List updateSmartHomeDeviceList(boolean forceUpdate) { - Connection currentConnection = connection; - if (currentConnection == null) { - return Collections.emptyList(); + if (!forceUpdate && smartHomeDeviceHandlers.isEmpty() && handlerConfig.discoverSmartHome == 0) { + return List.of(); } - if (!forceUpdate && smartHomeDeviceHandlers.isEmpty() && getSmartHomeDevicesDiscoveryMode() == 0) { - return Collections.emptyList(); - } - - List smartHomeDevices = null; try { - if (currentConnection.getIsLoggedIn()) { - smartHomeDevices = currentConnection.getSmarthomeDeviceList(); + if (connection.isLoggedIn()) { + JsonNetworkDetails networkDetails = connection.getRequestBuilder() + .get(connection.getAlexaServer() + "/api/phoenix").syncSend(JsonNetworkDetails.class); + Object jsonObject = gson.fromJson(networkDetails.networkDetail, Object.class); + List smartHomeDevices = new ArrayList<>(); + searchSmartHomeDevicesRecursive(jsonObject, smartHomeDevices); + + // create new id map + Map newJsonIdSmartHomeDeviceMapping = new HashMap<>(); + for (SmartHomeBaseDevice smartHomeDevice : smartHomeDevices) { + String id = smartHomeDevice.findId(); + if (id != null) { + newJsonIdSmartHomeDeviceMapping.put(id, smartHomeDevice); + } + } + jsonIdSmartHomeDeviceMapping = newJsonIdSmartHomeDeviceMapping; + + // update handlers + smartHomeDeviceHandlers + .forEach(child -> child.setDeviceAndUpdateThingState(this, findSmartHomeDeviceJson(child))); + return smartHomeDevices; } } catch (ConnectionException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); } - if (smartHomeDevices != null) { - // create new id map - Map newJsonIdSmartHomeDeviceMapping = new HashMap<>(); - for (SmartHomeBaseDevice smartHomeDevice : smartHomeDevices) { - String id = smartHomeDevice.findId(); - if (id != null) { - newJsonIdSmartHomeDeviceMapping.put(id, smartHomeDevice); + + return List.of(); + } + + private void searchSmartHomeDevicesRecursive(@Nullable Object jsonNode, List devices) { + if (jsonNode instanceof Map) { + @SuppressWarnings({ "rawtypes", "unchecked" }) + Map map = (Map) jsonNode; + if (map.containsKey("entityId") && map.containsKey("friendlyName") && map.containsKey("actions")) { + // device node found, create type element and add it to the results + JsonElement element = gson.toJsonTree(jsonNode); + JsonSmartHomeDevice shd = parseJson(element.toString(), JsonSmartHomeDevice.class); + if (shd != null) { + devices.add(shd); } + } else if (map.containsKey("applianceGroupName")) { + JsonElement element = gson.toJsonTree(jsonNode); + JsonSmartHomeGroups.SmartHomeGroup shg = parseJson(element.toString(), + JsonSmartHomeGroups.SmartHomeGroup.class); + if (shg != null) { + devices.add(shg); + } + } else { + map.values().forEach(value -> searchSmartHomeDevicesRecursive(value, devices)); } - jsonIdSmartHomeDeviceMapping = newJsonIdSmartHomeDeviceMapping; } - // update handlers - smartHomeDeviceHandlers - .forEach(child -> child.setDeviceAndUpdateThingState(this, findSmartHomeDeviceJson(child))); + } - return Objects.requireNonNullElse(smartHomeDevices, List.of()); + // parser + private @Nullable T parseJson(String json, Class type) throws JsonSyntaxException, IllegalStateException { + try { + // gson.fromJson is non-null if json is non-null and not empty + return gson.fromJson(json, type); + } catch (JsonParseException | IllegalStateException e) { + logger.warn("Parsing json failed: {}", json, e); + throw e; + } } public void forceDelayedSmartHomeStateUpdate(String deviceId) { @@ -889,8 +770,7 @@ private void updateSmartHomeStateJob() { Set deviceUpdates = new HashSet<>(); synchronized (synchronizeSmartHomeJobScheduler) { - Connection connection = this.connection; - if (connection == null || !connection.getIsLoggedIn()) { + if (!connection.isLoggedIn()) { this.refreshSmartHomeAfterCommandJob = scheduler.schedule(this::updateSmartHomeStateJob, 1000, TimeUnit.MILLISECONDS); return; @@ -905,8 +785,7 @@ private void updateSmartHomeStateJob() { private synchronized void updateSmartHomeState(@Nullable String deviceFilterId) { try { logger.trace("updateSmartHomeState started with deviceFilterId={}", deviceFilterId); - Connection connection = this.connection; - if (connection == null || !connection.getIsLoggedIn()) { + if (!connection.isLoggedIn()) { return; } List allDevices = getLastKnownSmartHomeDevices(); @@ -962,4 +841,12 @@ private synchronized void updateSmartHomeState(@Nullable String deviceFilterId) public Collection> getServices() { return Set.of(AmazonEchoDiscovery.class, SmartHomeDevicesDiscovery.class); } + + @Override + public void onPushConnectionStateChange(PushConnection.State state) { + if (!disposing && state == CLOSED) { + // force check of login + nextLoginCheck = 0; + } + } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/EchoHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/EchoHandler.java index 32b1ec536b..739713c404 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/EchoHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/EchoHandler.java @@ -13,22 +13,23 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.handler; +import static org.eclipse.jetty.util.StringUtil.isNotBlank; import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; +import static org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushAudioPlayerStateTO.AudioPlayerState.*; +import static org.smarthomej.binding.amazonechocontrol.internal.util.Util.findIn; import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -48,9 +49,10 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.openhab.core.types.CommandOption; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; import org.openhab.core.types.StateDescription; @@ -60,39 +62,38 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.AmazonHandlerCallback; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.ChannelHandler; -import org.smarthomej.binding.amazonechocontrol.internal.channelhandler.ChannelHandlerAnnouncement; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonAscendingAlarm.AscendingAlarmModel; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushNotificationChange; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCommandPayloadPushVolumeChange; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCustomerHistoryRecords.CustomerHistoryRecord; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonCustomerHistoryRecords.CustomerHistoryRecord.VoiceHistoryRecordItem; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDeviceNotificationState.DeviceNotificationState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDoNotDisturb.DoNotDisturbDeviceStatus; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonEqualizer; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMediaState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMediaState.QueueEntry; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonMusicProvider; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationResponse; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo.InfoText; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo.MainArt; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo.Progress; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo.Provider; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlayerState.PlayerInfo.Volume; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPlaylists; -import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; +import org.smarthomej.binding.amazonechocontrol.internal.dto.AscendingAlarmModelTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.BluetoothPairedDeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceNotificationStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DoNotDisturbDeviceStatusTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EqualizerTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationSoundTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.NotificationTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateInfoTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateInfoTextTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateMainArtTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateProgressTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateProviderTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.PlayerStateVolumeTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushAudioPlayerStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushEqualizerStateChangeTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushVolumeChangeTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.PlayerSeekMediaTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.request.WHAVolumeLevelTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.CustomerHistoryRecordVoiceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MediaSessionTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.MusicProviderTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.response.PlayerStateTO; +import org.smarthomej.binding.amazonechocontrol.internal.types.Announcement; +import org.smarthomej.binding.amazonechocontrol.internal.types.Notification; import org.smarthomej.commons.SimpleDynamicStateDescriptionProvider; -import org.smarthomej.commons.UpdatingBaseThingHandler; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; /** * The {@link EchoHandler} is responsible for the handling of the echo device @@ -100,83 +101,69 @@ * @author Michael Geramb - Initial contribution */ @NonNullByDefault -public class EchoHandler extends UpdatingBaseThingHandler implements AmazonHandlerCallback { +public class EchoHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(EchoHandler.class); private final Gson gson; private final SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider; - private final SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider; - private @Nullable Device device; + private @Nullable DeviceTO device; private Set capabilities = new HashSet<>(); - private @Nullable AccountHandler account; + private @Nullable AccountHandler account = null; private @Nullable ScheduledFuture updateStateJob; private @Nullable ScheduledFuture updateProgressJob; private final Object progressLock = new Object(); private @Nullable String wakeWord; - private @Nullable String lastKnownRadioStationId; private @Nullable String lastKnownBluetoothMAC; - private @Nullable String lastKnownAmazonMusicId; + private long lastCustomerHistoryRecordTimestamp = System.currentTimeMillis(); private String musicProviderId = "TUNEIN"; private boolean isPlaying = false; private boolean isPaused = false; private int lastKnownVolume = 25; private int textToSpeechVolume = 0; - private @Nullable JsonEqualizer lastKnownEqualizer = null; + private @Nullable EqualizerTO lastKnownEqualizer = null; private boolean disableUpdate = false; - private boolean updateRemind = true; - private boolean updateTextToSpeech = true; - private boolean updateTextCommand = true; - private boolean updateRoutine = true; - private boolean updatePlayMusicVoiceCommand = true; - private @Nullable Integer notificationVolumeLevel; - private @Nullable Boolean ascendingAlarm; - private @Nullable Boolean doNotDisturb; - private final List channelHandlers = new ArrayList<>(); - - private @Nullable JsonNotificationResponse currentNotification; - private @Nullable ScheduledFuture currentNotifcationUpdateTimer; - long mediaLengthMs; - long mediaProgressMs; - long mediaStartMs; + + private @Nullable NotificationTO currentNotification; + private @Nullable ScheduledFuture currentNotificationUpdateTimer; + private long mediaLengthMs; + private long mediaProgressMs; + private long mediaStartMs; + + private String currentlyPlayingQueueId = ""; + + // used to block further updates when an update is already taking place + private final AtomicBoolean waitingForUpdate = new AtomicBoolean(false); private final ExpiringCacheMap stateCache = new ExpiringCacheMap<>(Duration.ofSeconds(30)); - public EchoHandler(Thing thing, Gson gson, - SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider, - SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider) { + public EchoHandler(Thing thing, Gson gson, SimpleDynamicStateDescriptionProvider dynamicStateDescriptionProvider) { super(thing); this.gson = gson; - this.dynamicCommandDescriptionProvider = dynamicCommandDescriptionProvider; this.dynamicStateDescriptionProvider = dynamicStateDescriptionProvider; - channelHandlers.add(new ChannelHandlerAnnouncement(this, this.gson)); } @Override public void initialize() { - logger.debug("Amazon Echo Control Binding initialized"); Bridge bridge = this.getBridge(); if (bridge != null) { - AccountHandler account = (AccountHandler) bridge.getHandler(); - if (account != null) { - setDeviceAndUpdateThingState(account, this.device, null); - createStartCommandCommandOptions(account.getFlashBriefingProfileHandlers()); - account.addEchoHandler(this); - } + account = (AccountHandler) bridge.getHandler(); + } + if (account == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge handler not found."); + } else { + updateStatus(ThingStatus.UNKNOWN); } + + lastCustomerHistoryRecordTimestamp = System.currentTimeMillis(); } - public boolean setDeviceAndUpdateThingState(AccountHandler accountHandler, @Nullable Device device, - @Nullable String wakeWord) { - this.account = accountHandler; + public boolean setDeviceAndUpdateThingStatus(DeviceTO device, @Nullable String wakeWord) { if (wakeWord != null) { this.wakeWord = wakeWord; } - if (device == null) { - updateStatus(ThingStatus.UNKNOWN); - return false; - } + this.device = device; - this.capabilities = device.getCapabilities(); + this.capabilities = device.capabilities; if (!device.online) { updateStatus(ThingStatus.OFFLINE); return false; @@ -187,13 +174,6 @@ public boolean setDeviceAndUpdateThingState(AccountHandler accountHandler, @Null @Override public void dispose() { - Bridge bridge = this.getBridge(); - if (bridge != null) { - AccountHandler account = (AccountHandler) bridge.getHandler(); - if (account != null) { - account.removeEchoHandler(this); - } - } stopCurrentNotification(); ScheduledFuture updateStateJob = this.updateStateJob; this.updateStateJob = null; @@ -212,24 +192,17 @@ private void stopProgressTimer() { } } - private @Nullable Connection findConnection() { - AccountHandler accountHandler = this.account; - if (accountHandler != null) { - return accountHandler.findConnection(); - } - return null; + private Optional getAccountHandler() { + return Optional.ofNullable(account); } - public @Nullable Device findDevice() { - return this.device; + private Optional findConnection() { + return getAccountHandler().map(AccountHandler::getConnection); } - public String findSerialNumber() { + public String getSerialNumber() { String id = (String) getConfig().get(DEVICE_PROPERTY_SERIAL_NUMBER); - if (id == null) { - return ""; - } - return id; + return id != null ? id : ""; } @Override @@ -241,6 +214,12 @@ public void handleCommand(ChannelUID channelUID, Command command) { // call the original update method to prevent prolonging the same value in cache super.updateState(channelUID, state); return; + } else { + getAccountHandler().ifPresent(accountHandler -> { + if (waitingForUpdate.compareAndSet(false, true)) { + accountHandler.forceCheckData(); + } + }); } } try { @@ -254,97 +233,127 @@ public void handleCommand(ChannelUID channelUID, Command command) { this.disableUpdate = false; updateStateJob.cancel(false); } - AccountHandler account = this.account; - if (account == null) { + + AccountHandler accountHandler = getAccountHandler().orElse(null); + if (accountHandler == null) { return; } - Connection connection = account.findConnection(); + Connection connection = findConnection().orElse(null); if (connection == null) { return; } - Device device = this.device; + DeviceTO device = this.device; if (device == null) { return; } String channelId = channelUID.getId(); - for (ChannelHandler channelHandler : channelHandlers) { - if (channelHandler.tryHandleCommand(device, connection, channelId, command)) { - return; + if (channelId.equals(CHANNEL_ANNOUNCEMENT) && command instanceof StringType) { + String commandValue = command.toFullString(); + String body = commandValue; + String title = null; + String speak = commandValue; + Integer volume = null; + if (commandValue.startsWith("{") && commandValue.endsWith("}")) { + try { + Announcement request = gson.fromJson(commandValue, Announcement.class); + if (request != null) { + speak = isNotBlank(request.speak) ? request.speak : "."; // generate beep if no text + Objects.requireNonNull(speak); // fix the null-checker + volume = request.volume; + title = request.title; + body = request.body != null ? request.body : speak; + Boolean sound = request.sound; + if (sound != null) { + if (!sound && !speak.startsWith("")) { + speak = "" + speak + ""; + } + if (sound && speak.startsWith("")) { + body = "Error: The combination of sound and speak in SSML syntax is not allowed"; + title = "Error"; + speak = "Error: The combination of sound and speak in SSML syntax is not allowed"; + } + } + if (" ".equals(speak)) { + volume = -1; // Do not change volume + } + } + } catch (JsonSyntaxException e) { + body = "Invalid Json." + e.getLocalizedMessage(); + title = "Error"; + speak = "" + body + ""; + body = e.getLocalizedMessage(); + } + } + Integer vol; + if (volume == null && textToSpeechVolume != 0) { + vol = textToSpeechVolume; + } else if (volume != null && volume < 0) { + vol = null;// the meaning of negative values is 'do not use'. The api requires null in this case. + } else { + vol = volume; } + String finalSpeak = speak; + String finalBody = Objects.requireNonNullElse(body, ""); + String finalTitle = title; + connection.announcement(device, finalSpeak, finalBody, finalTitle, vol, lastKnownVolume); } // Player commands if (channelId.equals(CHANNEL_PLAYER)) { if (command == PlayPauseType.PAUSE || command == OnOffType.OFF) { - connection.command(device, "{\"type\":\"PauseCommand\"}"); + connection.command(device, Map.of("type", "PauseCommand")); } else if (command == PlayPauseType.PLAY || command == OnOffType.ON) { if (isPaused) { - connection.command(device, "{\"type\":\"PlayCommand\"}"); + connection.command(device, Map.of("type", "PlayCommand")); } else { connection.playMusicVoiceCommand(device, this.musicProviderId, "!"); waitForUpdate = 3000; } } else if (command == NextPreviousType.NEXT) { - connection.command(device, "{\"type\":\"NextCommand\"}"); + connection.command(device, Map.of("type", "NextCommand")); } else if (command == NextPreviousType.PREVIOUS) { - connection.command(device, "{\"type\":\"PreviousCommand\"}"); + connection.command(device, Map.of("type", "PreviousCommand")); } else if (command == RewindFastforwardType.FASTFORWARD) { - connection.command(device, "{\"type\":\"ForwardCommand\"}"); + connection.command(device, Map.of("type", "ForwardCommand")); } else if (command == RewindFastforwardType.REWIND) { - connection.command(device, "{\"type\":\"RewindCommand\"}"); + connection.command(device, Map.of("type", "RewindCommand")); } } // Notification commands if (channelId.equals(CHANNEL_NOTIFICATION_VOLUME)) { - if (command instanceof PercentType) { - int volume = ((PercentType) command).intValue(); - connection.notificationVolume(device, volume); - this.notificationVolumeLevel = volume; + if (command instanceof PercentType percent) { + connection.setNotificationVolume(device, percent.intValue()); waitForUpdate = -1; - account.forceCheckData(); + accountHandler.forceCheckData(); } } if (channelId.equals(CHANNEL_ASCENDING_ALARM)) { - if (command == OnOffType.OFF) { - connection.ascendingAlarm(device, false); - this.ascendingAlarm = false; - waitForUpdate = -1; - account.forceCheckData(); - } - if (command == OnOffType.ON) { - connection.ascendingAlarm(device, true); - this.ascendingAlarm = true; - waitForUpdate = -1; - account.forceCheckData(); - } + boolean ascendingAlarm = command == OnOffType.ON; + connection.setAscendingAlarm(device, ascendingAlarm); + waitForUpdate = -1; + accountHandler.forceCheckData(); } // Do Not Disturb command if (channelId.equals(CHANNEL_DO_NOT_DISTURB) && command instanceof OnOffType) { - boolean newDnd = OnOffType.ON.equals(command); - connection.doNotDisturb(device, newDnd); - this.doNotDisturb = true; + boolean newDnd = command == OnOffType.ON; + connection.setDoNotDisturb(device, newDnd); waitForUpdate = -1; - account.forceCheckData(); + accountHandler.forceCheckData(); } // Media progress commands Long mediaPosition = null; if (channelId.equals(CHANNEL_MEDIA_PROGRESS)) { - if (command instanceof PercentType) { - PercentType value = (PercentType) command; - int percent = value.intValue(); - mediaPosition = Math.round((mediaLengthMs / 1000d) * (percent / 100d)); + if (command instanceof PercentType percent) { + mediaPosition = Math.round((mediaLengthMs / 1000d) * (percent.intValue() / 100d)); } } if (channelId.equals(CHANNEL_MEDIA_PROGRESS_TIME)) { - if (command instanceof DecimalType) { - DecimalType value = (DecimalType) command; - mediaPosition = value.longValue(); + if (command instanceof DecimalType decimal) { + mediaPosition = decimal.longValue(); } - if (command instanceof QuantityType) { - QuantityType value = (QuantityType) command; - @Nullable - QuantityType seconds = value.toUnit(Units.SECOND); + if (command instanceof QuantityType quantity) { + QuantityType seconds = quantity.toUnit(Units.SECOND); if (seconds != null) { mediaPosition = seconds.longValue(); } @@ -353,8 +362,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (mediaPosition != null) { waitForUpdate = -1; synchronized (progressLock) { - String seekCommand = "{\"type\":\"SeekCommand\",\"mediaPosition\":" + mediaPosition - + ",\"contentFocusClientId\":null}"; + PlayerSeekMediaTO seekCommand = new PlayerSeekMediaTO(); + seekCommand.mediaPosition = mediaPosition; connection.command(device, seekCommand); connection.command(device, seekCommand); // Must be sent twice, the first one is ignored sometimes this.mediaProgressMs = mediaPosition * 1000; @@ -365,8 +374,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { // Volume commands if (channelId.equals(CHANNEL_VOLUME)) { Integer volume = null; - if (command instanceof PercentType) { - PercentType value = (PercentType) command; + if (command instanceof PercentType value) { volume = value.intValue(); } else if (command == OnOffType.OFF) { volume = 0; @@ -385,10 +393,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { } if (volume != null) { if ("WHA".equals(device.deviceFamily)) { - connection.command(device, "{\"type\":\"VolumeLevelCommand\",\"volumeLevel\":" + volume - + ",\"contentFocusClientId\":\"Default\"}"); + WHAVolumeLevelTO volumeCommand = new WHAVolumeLevelTO(); + volumeCommand.volumeLevel = volume; + connection.command(device, volumeCommand); } else { - connection.volume(device, volume); + connection.setVolume(device, volume); } lastKnownVolume = volume; updateState(CHANNEL_VOLUME, new PercentType(lastKnownVolume)); @@ -405,36 +414,28 @@ public void handleCommand(ChannelUID channelUID, Command command) { // shuffle command if (channelId.equals(CHANNEL_SHUFFLE)) { - if (command instanceof OnOffType) { - OnOffType value = (OnOffType) command; - - connection.command(device, "{\"type\":\"ShuffleCommand\",\"shuffle\":\"" - + (value == OnOffType.ON ? "true" : "false") + "\"}"); + if (command instanceof OnOffType onOff) { + connection.command(device, Map.of("type", "ShuffleCommand", "shuffle", onOff == OnOffType.ON)); } } // play music command - if (channelId.equals(CHANNEL_MUSIC_PROVIDER_ID)) { - if (command instanceof StringType) { - waitForUpdate = 0; - String musicProviderId = command.toFullString(); - if (!musicProviderId.equals(this.musicProviderId)) { - this.musicProviderId = musicProviderId; - if (this.isPlaying) { - connection.playMusicVoiceCommand(device, this.musicProviderId, "!"); - waitForUpdate = 3000; - } + if (channelId.equals(CHANNEL_MUSIC_PROVIDER_ID) && command instanceof StringType) { + waitForUpdate = 0; + String musicProviderId = command.toFullString(); + if (!musicProviderId.equals(this.musicProviderId)) { + this.musicProviderId = musicProviderId; + if (this.isPlaying) { + connection.playMusicVoiceCommand(device, this.musicProviderId, "!"); + waitForUpdate = 3000; } } } - if (channelId.equals(CHANNEL_PLAY_MUSIC_VOICE_COMMAND)) { - if (command instanceof StringType) { - String voiceCommand = command.toFullString(); - if (!this.musicProviderId.isEmpty()) { - connection.playMusicVoiceCommand(device, this.musicProviderId, voiceCommand); - waitForUpdate = 3000; - updatePlayMusicVoiceCommand = true; - } + if (channelId.equals(CHANNEL_PLAY_MUSIC_VOICE_COMMAND) && command instanceof StringType) { + String voiceCommand = command.toFullString(); + if (!this.musicProviderId.isEmpty()) { + connection.playMusicVoiceCommand(device, this.musicProviderId, voiceCommand); + waitForUpdate = 3000; } } @@ -464,110 +465,49 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (channelId.equals(CHANNEL_BLUETOOTH_DEVICE_NAME)) { needBluetoothRefresh = true; } - // amazon music commands - if (channelId.equals(CHANNEL_AMAZON_MUSIC_TRACK_ID)) { - if (command instanceof StringType) { - String trackId = command.toFullString(); - if (!trackId.isEmpty()) { - waitForUpdate = 3000; - } - connection.playAmazonMusicTrack(device, trackId); - } - } - if (channelId.equals(CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID)) { - if (command instanceof StringType) { - String playListId = command.toFullString(); - if (!playListId.isEmpty()) { - waitForUpdate = 3000; - } - connection.playAmazonMusicPlayList(device, playListId); - } - } - if (channelId.equals(CHANNEL_AMAZON_MUSIC)) { - if (command == OnOffType.ON) { - String lastKnownAmazonMusicId = this.lastKnownAmazonMusicId; - if (lastKnownAmazonMusicId != null && !lastKnownAmazonMusicId.isEmpty()) { - waitForUpdate = 3000; - } - connection.playAmazonMusicTrack(device, lastKnownAmazonMusicId); - } else if (command == OnOffType.OFF) { - connection.playAmazonMusicTrack(device, ""); - } - } - - // radio commands - if (channelId.equals(CHANNEL_RADIO_STATION_ID)) { - if (command instanceof StringType) { - String stationId = command.toFullString(); - if (!stationId.isEmpty()) { - waitForUpdate = 3000; - } - connection.playRadio(device, stationId); - } - } - if (channelId.equals(CHANNEL_RADIO)) { - if (command == OnOffType.ON) { - String lastKnownRadioStationId = this.lastKnownRadioStationId; - if (lastKnownRadioStationId != null && !lastKnownRadioStationId.isEmpty()) { - waitForUpdate = 3000; - } - connection.playRadio(device, lastKnownRadioStationId); - } else if (command == OnOffType.OFF) { - connection.playRadio(device, ""); - } - } // notification - if (channelId.equals(CHANNEL_REMIND)) { - if (command instanceof StringType) { - stopCurrentNotification(); - String reminder = command.toFullString(); - if (!reminder.isEmpty()) { - waitForUpdate = 3000; - updateRemind = true; - currentNotification = connection.notification(device, "Reminder", reminder, null); - currentNotifcationUpdateTimer = scheduler - .scheduleWithFixedDelay(this::updateNotificationTimerState, 1, 1, TimeUnit.SECONDS); - } - } - } - if (channelId.equals(CHANNEL_PLAY_ALARM_SOUND)) { - if (command instanceof StringType) { - stopCurrentNotification(); - String alarmSound = command.toFullString(); - if (!alarmSound.isEmpty()) { - waitForUpdate = 3000; - String[] parts = alarmSound.split(":", 2); - JsonNotificationSound sound = new JsonNotificationSound(); - if (parts.length == 2) { - sound.providerId = parts[0]; - sound.id = parts[1]; - } else { - sound.providerId = "ECHO"; - sound.id = alarmSound; - } - currentNotification = connection.notification(device, "Alarm", null, sound); - currentNotifcationUpdateTimer = scheduler - .scheduleWithFixedDelay(this::updateNotificationTimerState, 1, 1, TimeUnit.SECONDS); + if (channelId.equals(CHANNEL_REMIND) && command instanceof StringType) { + stopCurrentNotification(); + String reminder = command.toFullString(); + if (!reminder.isBlank()) { + waitForUpdate = 3000; + currentNotification = connection.createNotification(device, "Reminder", reminder, null); + currentNotificationUpdateTimer = scheduler + .scheduleWithFixedDelay(this::updateNotificationTimerState, 1, 1, TimeUnit.SECONDS); + } + } + if (channelId.equals(CHANNEL_PLAY_ALARM_SOUND) && command instanceof StringType) { + stopCurrentNotification(); + String alarmSound = command.toFullString(); + if (!alarmSound.isEmpty()) { + waitForUpdate = 3000; + String[] parts = alarmSound.split(":", 2); + NotificationSoundTO sound = new NotificationSoundTO(); + if (parts.length == 2) { + sound.providerId = parts[0]; + sound.id = parts[1]; + } else { + sound.providerId = "ECHO"; + sound.id = alarmSound; } + currentNotification = connection.createNotification(device, "Alarm", null, sound); + currentNotificationUpdateTimer = scheduler + .scheduleWithFixedDelay(this::updateNotificationTimerState, 1, 1, TimeUnit.SECONDS); } } // routine commands - if (channelId.equals(CHANNEL_TEXT_TO_SPEECH)) { - if (command instanceof StringType) { - String text = command.toFullString(); - if (!text.isEmpty()) { - waitForUpdate = 1000; - updateTextToSpeech = true; - startTextToSpeech(connection, device, text); - } + if (channelId.equals(CHANNEL_TEXT_TO_SPEECH) && command instanceof StringType) { + String text = command.toFullString(); + if (!text.isEmpty()) { + waitForUpdate = 1000; + startTextToSpeech(connection, device, text); } } if (channelId.equals(CHANNEL_TEXT_TO_SPEECH_VOLUME)) { - if (command instanceof PercentType) { - PercentType value = (PercentType) command; - textToSpeechVolume = value.intValue(); + if (command instanceof PercentType percent) { + textToSpeechVolume = percent.intValue(); } else if (command == OnOffType.OFF) { textToSpeechVolume = 0; } else if (command == OnOffType.ON) { @@ -583,61 +523,48 @@ public void handleCommand(ChannelUID channelUID, Command command) { } this.updateState(channelId, new PercentType(textToSpeechVolume)); } - if (channelId.equals(CHANNEL_TEXT_COMMAND)) { - if (command instanceof StringType) { - String text = command.toFullString(); - if (!text.isEmpty()) { - waitForUpdate = 1000; - updateTextCommand = true; - startTextCommand(connection, device, text); - } - } - } - if (channelId.equals(CHANNEL_LAST_VOICE_COMMAND)) { - if (command instanceof StringType) { - String text = command.toFullString(); - if (!text.isEmpty()) { - waitForUpdate = -1; - startTextToSpeech(connection, device, text); - } + if (channelId.equals(CHANNEL_TEXT_COMMAND) && command instanceof StringType) { + String text = command.toFullString(); + if (!text.isEmpty()) { + waitForUpdate = 1000; + startTextCommand(connection, device, text); } } - if (channelId.equals(CHANNEL_START_COMMAND)) { - if (command instanceof StringType) { - String commandText = command.toFullString(); - if (!commandText.isEmpty()) { - if (commandText.startsWith(FLASH_BRIEFING_COMMAND_PREFIX)) { - // Handle custom flashbriefings commands - String flashBriefingId = commandText.substring(FLASH_BRIEFING_COMMAND_PREFIX.length()); - for (FlashBriefingProfileHandler flashBriefingHandler : account - .getFlashBriefingProfileHandlers()) { - ThingUID flashBriefingUid = flashBriefingHandler.getThing().getUID(); - if (flashBriefingId.equals(flashBriefingHandler.getThing().getUID().getId())) { - flashBriefingHandler.handleCommand( - new ChannelUID(flashBriefingUid, CHANNEL_PLAY_ON_DEVICE), - new StringType(device.serialNumber)); - break; - } - } - } else { - // Handle standard commands - if (!commandText.startsWith("Alexa.")) { - commandText = "Alexa." + commandText + ".Play"; - } - waitForUpdate = 1000; - connection.executeSequenceCommand(device, commandText, Map.of()); + if (channelId.equals(CHANNEL_LAST_VOICE_COMMAND) && command instanceof StringType) { + String text = command.toFullString(); + if (!text.isEmpty()) { + waitForUpdate = -1; + startTextToSpeech(connection, device, text); + } + } + if (channelId.equals(CHANNEL_START_COMMAND) && command instanceof StringType) { + String commandText = command.toFullString(); + if (commandText.startsWith(FLASH_BRIEFING_COMMAND_PREFIX)) { + // Handle custom flashbriefings commands + String flashBriefingId = commandText.substring(FLASH_BRIEFING_COMMAND_PREFIX.length()); + for (FlashBriefingProfileHandler flashBriefingHandler : accountHandler + .getFlashBriefingProfileHandlers()) { + ThingUID flashBriefingUid = flashBriefingHandler.getThing().getUID(); + if (flashBriefingId.equals(flashBriefingHandler.getThing().getUID().getId())) { + flashBriefingHandler.handleCommand(new ChannelUID(flashBriefingUid, CHANNEL_PLAY_ON_DEVICE), + new StringType(device.serialNumber)); + break; } } + } else if (!commandText.isBlank()) { + // Handle standard commands + if (!commandText.startsWith("Alexa.")) { + commandText = "Alexa." + commandText + ".Play"; + } + waitForUpdate = 1000; + connection.executeSequenceCommand(device, commandText, Map.of()); } } - if (channelId.equals(CHANNEL_START_ROUTINE)) { - if (command instanceof StringType) { - String utterance = command.toFullString(); - if (!utterance.isEmpty()) { - waitForUpdate = 1000; - updateRoutine = true; - connection.startRoutine(device, utterance); - } + if (channelId.equals(CHANNEL_START_ROUTINE) && command instanceof StringType) { + String utterance = command.toFullString(); + if (!utterance.isEmpty()) { + waitForUpdate = 1000; + connection.startRoutine(device, utterance); } } if (waitForUpdate < 0) { @@ -648,16 +575,13 @@ public void handleCommand(ChannelUID channelUID, Command command) { final boolean bluetoothRefresh = needBluetoothRefresh; Runnable doRefresh = () -> { this.disableUpdate = false; - BluetoothState state = null; + BluetoothStateTO state = null; if (bluetoothRefresh) { - state = connection.getBluetoothConnectionStates().findStateByDevice(device); + List states = connection.getBluetoothConnectionStates(); + state = findIn(states, a -> a.deviceSerialNumber, device.serialNumber).orElse(null); } - updateState(account, device, state, null, null, null, null, null, null); + updateState(device, state, null, null, null, null); }; - if (command instanceof RefreshType) { - waitForUpdate = 0; - account.forceCheckData(); - } if (waitForUpdate == 0) { doRefresh.run(); } else { @@ -665,126 +589,82 @@ public void handleCommand(ChannelUID channelUID, Command command) { } } catch (ConnectionException e) { logger.info("Failed to handle command '{}' to '{}'", command, channelUID); + } catch (RuntimeException e) { + logger.warn("RuntimeException in handle command", e); } } - private boolean handleEqualizerCommands(String channelId, Command command, Connection connection, Device device) { - if (command instanceof RefreshType) { - this.lastKnownEqualizer = null; - } - if (command instanceof DecimalType) { - DecimalType value = (DecimalType) command; - if (this.lastKnownEqualizer == null) { + private boolean handleEqualizerCommands(String channelId, Command command, Connection connection, DeviceTO device) { + if (command instanceof DecimalType decimal) { + if (lastKnownEqualizer == null) { updateEqualizerState(); } - JsonEqualizer lastKnownEqualizer = this.lastKnownEqualizer; - if (lastKnownEqualizer != null) { - JsonEqualizer newEqualizerSetting = lastKnownEqualizer.createClone(); - if (channelId.equals(CHANNEL_EQUALIZER_BASS)) { - newEqualizerSetting.bass = value.intValue(); - } - if (channelId.equals(CHANNEL_EQUALIZER_MIDRANGE)) { - newEqualizerSetting.mid = value.intValue(); - } - if (channelId.equals(CHANNEL_EQUALIZER_TREBLE)) { - newEqualizerSetting.treble = value.intValue(); - } - try { - connection.setEqualizer(device, newEqualizerSetting); - return true; - } catch (ConnectionException e) { - logger.debug("Failed to update equalizer"); - this.lastKnownEqualizer = null; - } + EqualizerTO oldEqualizer = lastKnownEqualizer; + if (oldEqualizer != null) { + EqualizerTO newEqualizer = new EqualizerTO(); + newEqualizer.bass = channelId.equals(CHANNEL_EQUALIZER_BASS) ? decimal.intValue() : oldEqualizer.bass; + newEqualizer.mid = channelId.equals(CHANNEL_EQUALIZER_MIDRANGE) ? decimal.intValue() : oldEqualizer.mid; + newEqualizer.treble = channelId.equals(CHANNEL_EQUALIZER_TREBLE) ? decimal.intValue() + : oldEqualizer.treble; + return connection.setEqualizer(device, newEqualizer); } } return false; } - private void startTextToSpeech(Connection connection, Device device, String text) { + private void startTextToSpeech(Connection connection, DeviceTO device, String text) { Integer volume = textToSpeechVolume != 0 ? textToSpeechVolume : null; connection.textToSpeech(device, text, volume, lastKnownVolume); } - private void startTextCommand(Connection connection, Device device, String text) { + private void startTextCommand(Connection connection, DeviceTO device, String text) { Integer volume = textToSpeechVolume != 0 ? textToSpeechVolume : null; connection.textCommand(device, text, volume, lastKnownVolume); } - @Override - public void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title, - @Nullable Integer volume) { - Connection connection = this.findConnection(); - if (connection == null) { - return; - } - if (volume == null && textToSpeechVolume != 0) { - volume = textToSpeechVolume; - } - if (volume != null && volume < 0) { - volume = null; // the meaning of negative values is 'do not use'. The api requires null in this case. - } - connection.announcement(device, speak, bodyText, title, volume, lastKnownVolume); - } - private void stopCurrentNotification() { - ScheduledFuture currentNotificationUpdateTimer = this.currentNotifcationUpdateTimer; + ScheduledFuture currentNotificationUpdateTimer = this.currentNotificationUpdateTimer; if (currentNotificationUpdateTimer != null) { - this.currentNotifcationUpdateTimer = null; - currentNotificationUpdateTimer.cancel(true); + this.currentNotificationUpdateTimer = null; + // do not interrupt the current set, otherwise the DELETE request will be aborted + currentNotificationUpdateTimer.cancel(false); } - JsonNotificationResponse currentNotification = this.currentNotification; + NotificationTO currentNotification = this.currentNotification; if (currentNotification != null) { this.currentNotification = null; - Connection currentConnection = this.findConnection(); - if (currentConnection != null) { - try { - currentConnection.stopNotification(currentNotification); - } catch (ConnectionException e) { - logger.warn("Failed to stop notification"); - } - } + findConnection().ifPresent(connection -> connection.deleteNotification(currentNotification.id)); } } private void updateNotificationTimerState() { boolean stopCurrentNotification = true; - JsonNotificationResponse currentNotification = this.currentNotification; + NotificationTO currentNotification = this.currentNotification; + Connection currentConnection = this.findConnection().orElse(null); try { - if (currentNotification != null) { - Connection currentConnection = this.findConnection(); - if (currentConnection != null) { - JsonNotificationResponse newState = currentConnection.getNotificationState(currentNotification); - if (newState != null && "ON".equals(newState.status)) { - stopCurrentNotification = false; - } + if (currentNotification != null && currentConnection != null) { + String status = currentConnection.getNotification(currentNotification.id).status; + if ("ON".equals(status)) { + stopCurrentNotification = false; } } } catch (ConnectionException e) { logger.warn("Failed to update notification state"); } if (stopCurrentNotification) { - if (currentNotification != null) { - String type = currentNotification.type; - if ("Reminder".equals(type)) { - updateState(CHANNEL_REMIND, StringType.EMPTY); - updateRemind = false; - } - } stopCurrentNotification(); } } - private void createMusicProviderStateDescription(List jsonMusicProviders) { + private void createMusicProviderStateDescription(List musicProviders) { List options = new ArrayList<>(); - for (JsonMusicProvider musicProvider : jsonMusicProviders) { - List properties = musicProvider.supportedProperties; + for (MusicProviderTO musicProvider : musicProviders) { + if (!musicProvider.supportedProperties.contains("Alexa.Music.PlaySearchPhrase")) { + continue; + } String providerId = musicProvider.id; String displayName = musicProvider.displayName; - if (properties != null && properties.contains("Alexa.Music.PlaySearchPhrase") && providerId != null - && !providerId.isEmpty() && "AVAILABLE".equals(musicProvider.availability) && displayName != null - && !displayName.isEmpty()) { - options.add(new StateOption(providerId, displayName)); + if (isNotBlank(providerId) && "AVAILABLE".equals(musicProvider.availability)) { + options.add(new StateOption(providerId, isNotBlank(displayName) ? displayName : providerId)); } } ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_MUSIC_PROVIDER_ID); @@ -796,60 +676,10 @@ private void createMusicProviderStateDescription(List jsonMus } } - private void createAlarmSoundsCommandOptions(List alarmSounds) { - List options = new ArrayList<>(); - for (JsonNotificationSound notificationSound : alarmSounds) { - if (notificationSound.folder == null && notificationSound.providerId != null && notificationSound.id != null - && notificationSound.displayName != null) { - String providerSoundId = notificationSound.providerId + ":" + notificationSound.id; - options.add(new CommandOption(providerSoundId, notificationSound.displayName)); - } - } - ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_PLAY_ALARM_SOUND); - dynamicCommandDescriptionProvider.setCommandOptions(channelUID, options); - } - - private void createPlaylistsCommandOptions(JsonPlaylists playlists) { - List options = new ArrayList<>(); - Map playlistMap = playlists.playlists; - if (playlistMap != null) { - for (JsonPlaylists.PlayList[] innerLists : playlistMap.values()) { - if (innerLists != null && innerLists.length > 0) { - JsonPlaylists.PlayList playList = innerLists[0]; - final String value = playList.playlistId; - if (value != null && playList.title != null) { - options.add(new CommandOption(value, - String.format("%s (%d)", playList.title, playList.trackCount))); - } - } - } - } - ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID); - dynamicCommandDescriptionProvider.setCommandOptions(channelUID, options); - } - - public void createStartCommandCommandOptions(Set flashBriefingProfileHandlers) { - List options = new ArrayList<>(); - options.add(new CommandOption("Weather", "Weather")); - options.add(new CommandOption("Traffic", "Traffic")); - options.add(new CommandOption("GoodMorning", "Good morning")); - options.add(new CommandOption("SingASong", "Song")); - options.add(new CommandOption("TellStory", "Story")); - options.add(new CommandOption("FlashBriefing", "Flash briefing")); - - for (FlashBriefingProfileHandler flashBriefing : flashBriefingProfileHandlers) { - String value = FLASH_BRIEFING_COMMAND_PREFIX + flashBriefing.getThing().getUID().getId(); - String displayName = flashBriefing.getThing().getLabel(); - options.add(new CommandOption(value, displayName)); - } - ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_START_COMMAND); - dynamicCommandDescriptionProvider.setCommandOptions(channelUID, options); - } - - private void createBluetoothMACStateDescription(BluetoothState bluetoothState) { + private void createBluetoothMACStateDescription(BluetoothStateTO bluetoothState) { List options = new ArrayList<>(); options.add(new StateOption("", "")); - for (PairedDevice device : bluetoothState.getPairedDeviceList()) { + for (BluetoothPairedDeviceTO device : bluetoothState.pairedDeviceList) { final String value = device.address; if (value != null && device.friendlyName != null) { options.add(new StateOption(value, device.friendlyName)); @@ -864,168 +694,182 @@ private void createBluetoothMACStateDescription(BluetoothState bluetoothState) { } } - public void updateState(AccountHandler accountHandler, @Nullable Device device, - @Nullable BluetoothState bluetoothState, @Nullable DeviceNotificationState deviceNotificationState, - @Nullable AscendingAlarmModel ascendingAlarmModel, - @Nullable DoNotDisturbDeviceStatus doNotDisturbDeviceStatus, @Nullable JsonPlaylists playlists, - @Nullable List alarmSounds, @Nullable List musicProviders) { - try { + private void updateMediaPlayerState(PlayerStateInfoTO playerInfo, boolean sequenceNodeRunning, int timeFactor) { + PlayerStateProviderTO provider = playerInfo.provider; + PlayerStateInfoTextTO infoText = playerInfo.infoText != null ? playerInfo.infoText : playerInfo.miniInfoText; + PlayerStateMainArtTO mainArt = playerInfo.mainArt; + String musicProviderId = null; + PlayerStateProgressTO progress = playerInfo.progress; + if (provider != null) { + musicProviderId = provider.providerName; + // Map the music provider id to the one used for starting music with voice command + if (musicProviderId != null) { + musicProviderId = musicProviderId.toUpperCase(); - this.logger.debug("Handle updateState {}", this.getThing().getUID()); + if ("AMAZON MUSIC".equals(musicProviderId) || "CLOUD_PLAYER".equals(musicProviderId)) { + musicProviderId = "AMAZON_MUSIC"; + } + if (musicProviderId.startsWith("TUNEIN")) { + musicProviderId = "TUNEIN"; + } + if (musicProviderId.startsWith("IHEARTRADIO")) { + musicProviderId = "I_HEART_RADIO"; + } + if (musicProviderId.startsWith("APPLE") && musicProviderId.contains("MUSIC")) { + musicProviderId = "APPLE_MUSIC"; + } + } + } + + // check playing + isPlaying = "PLAYING".equals(playerInfo.state); + isPaused = "PAUSED".equals(playerInfo.state); + + if (isPlaying) { + currentlyPlayingQueueId = playerInfo.queueId; + } + + synchronized (progressLock) { + if (isPlaying) { + if (progress != null) { + mediaProgressMs = progress.mediaProgress * timeFactor; + mediaLengthMs = progress.mediaLength * timeFactor; + mediaStartMs = System.currentTimeMillis() - mediaProgressMs; + } + if (updateProgressJob == null) { + updateProgressJob = scheduler.scheduleWithFixedDelay(() -> updateMediaProgress(false), 1000, 1000, + TimeUnit.MILLISECONDS); + } + } else { + stopProgressTimer(); + mediaProgressMs = 0; + mediaStartMs = 0; + mediaLengthMs = 0; + } + updateMediaProgress(true); + } + + // handle music provider id + if (musicProviderId != null && isPlaying) { + this.musicProviderId = musicProviderId; + } + + // handle title, subtitle, imageUrl + String title = ""; + String subTitle1 = ""; + String subTitle2 = ""; + String imageUrl = ""; + if (infoText != null) { + if (infoText.title != null) { + title = infoText.title; + } + if (infoText.subText1 != null) { + subTitle1 = infoText.subText1; + } + + if (infoText.subText2 != null) { + subTitle2 = infoText.subText2; + } + } + if (mainArt != null) { + if (mainArt.url != null) { + imageUrl = mainArt.url; + } + } + + // handle provider + String providerDisplayName = ""; + if (provider != null) { + providerDisplayName = Objects.requireNonNullElse(provider.providerDisplayName, providerDisplayName); + String providerName = provider.providerName; + if (isNotBlank(providerName) && providerDisplayName.isEmpty()) { + providerDisplayName = providerName; + } + } + + // handle volume + if (!sequenceNodeRunning) { + Integer volume = null; + PlayerStateVolumeTO volumeInfo = playerInfo.volume; + if (volumeInfo != null) { + volume = volumeInfo.volume; + } + if (volume != null && volume > 0) { + lastKnownVolume = volume; + updateState(CHANNEL_VOLUME, new PercentType(volume)); + } + } + + // Update states + updateState(CHANNEL_MUSIC_PROVIDER_ID, new StringType(musicProviderId)); + updateState(CHANNEL_PROVIDER_DISPLAY_NAME, new StringType(providerDisplayName)); + updateState(CHANNEL_PLAYER, isPlaying ? PlayPauseType.PLAY : PlayPauseType.PAUSE); + updateState(CHANNEL_IMAGE_URL, new StringType(imageUrl)); + updateState(CHANNEL_TITLE, new StringType(title)); + updateState(CHANNEL_SUBTITLE1, new StringType(subTitle1)); + updateState(CHANNEL_SUBTITLE2, new StringType(subTitle2)); + } + + public void updateState(DeviceTO device, @Nullable BluetoothStateTO bluetoothState, + @Nullable DeviceNotificationStateTO deviceNotificationState, + @Nullable AscendingAlarmModelTO ascendingAlarmModel, + @Nullable DoNotDisturbDeviceStatusTO doNotDisturbDeviceStatus, + @Nullable List musicProviders) { + try { + waitingForUpdate.set(false); + logger.debug("Handle updateState {}", this.getThing().getUID()); if (deviceNotificationState != null) { - notificationVolumeLevel = deviceNotificationState.volumeLevel; + int notificationVolumeLevel = deviceNotificationState.volumeLevel; + updateState(CHANNEL_NOTIFICATION_VOLUME, new PercentType(notificationVolumeLevel)); } + if (ascendingAlarmModel != null) { - ascendingAlarm = ascendingAlarmModel.ascendingAlarmEnabled; + boolean ascendingAlarm = ascendingAlarmModel.ascendingAlarmEnabled; + updateState(CHANNEL_ASCENDING_ALARM, OnOffType.from(ascendingAlarm)); } + if (doNotDisturbDeviceStatus != null) { - doNotDisturb = doNotDisturbDeviceStatus.enabled; - } - if (playlists != null) { - createPlaylistsCommandOptions(playlists); - } - if (alarmSounds != null) { - createAlarmSoundsCommandOptions(alarmSounds); + boolean doNotDisturb = doNotDisturbDeviceStatus.enabled; + updateState(CHANNEL_DO_NOT_DISTURB, OnOffType.from(doNotDisturb)); } + if (musicProviders != null) { createMusicProviderStateDescription(musicProviders); } - if (!setDeviceAndUpdateThingState(accountHandler, device, null)) { - this.logger.debug("Handle updateState {} aborted: Not online", this.getThing().getUID()); - return; - } - if (device == null) { - this.logger.debug("Handle updateState {} aborted: No device", this.getThing().getUID()); + + if (!setDeviceAndUpdateThingStatus(device, null)) { + logger.debug("Handle updateState {} aborted: Not online", this.getThing().getUID()); return; } - if (this.disableUpdate) { - this.logger.debug("Handle updateState {} aborted: Disabled", this.getThing().getUID()); + if (disableUpdate) { + logger.debug("Handle updateState {} aborted: Disabled", this.getThing().getUID()); return; } - Connection connection = this.findConnection(); + + Connection connection = findConnection().orElse(null); if (connection == null) { return; } - if (this.lastKnownEqualizer == null) { + if (lastKnownEqualizer == null) { updateEqualizerState(); } - PlayerInfo playerInfo = null; - Provider provider = null; - InfoText infoText = null; - MainArt mainArt = null; - String musicProviderId = null; - Progress progress = null; try { - JsonPlayerState playerState = connection.getPlayer(device); - if (playerState != null) { - playerInfo = playerState.playerInfo; - if (playerInfo != null) { - infoText = playerInfo.infoText; - if (infoText == null) { - infoText = playerInfo.miniInfoText; - } - mainArt = playerInfo.mainArt; - provider = playerInfo.provider; - if (provider != null) { - musicProviderId = provider.providerName; - // Map the music provider id to the one used for starting music with voice command - if (musicProviderId != null) { - musicProviderId = musicProviderId.toUpperCase(); - - if ("AMAZON MUSIC".equals(musicProviderId)) { - musicProviderId = "AMAZON_MUSIC"; - } - if ("CLOUD_PLAYER".equals(musicProviderId)) { - musicProviderId = "AMAZON_MUSIC"; - } - if (musicProviderId.startsWith("TUNEIN")) { - musicProviderId = "TUNEIN"; - } - if (musicProviderId.startsWith("IHEARTRADIO")) { - musicProviderId = "I_HEART_RADIO"; - } - if (musicProviderId.startsWith("APPLE") && musicProviderId.contains("MUSIC")) { - musicProviderId = "APPLE_MUSIC"; - } - } - } - progress = playerInfo.progress; - } - } + PlayerStateTO playerState = connection.getPlayerState(device); + updateMediaPlayerState(playerState.playerInfo, connection.isSequenceNodeQueueRunning(), 1000); } catch (ConnectionException e) { - logger.info("Failed to get player queue"); - } - // check playing - isPlaying = (playerInfo != null && "PLAYING".equals(playerInfo.state)); - - isPaused = (playerInfo != null && "PAUSED".equals(playerInfo.state)); - synchronized (progressLock) { - Boolean showTime = null; - Long mediaLength = null; - Long mediaProgress = null; - if (progress != null) { - showTime = progress.showTiming; - mediaLength = progress.mediaLength; - mediaProgress = progress.mediaProgress; - } - if (showTime != null && showTime && mediaProgress != null && mediaLength != null) { - mediaProgressMs = mediaProgress * 1000; - mediaLengthMs = mediaLength * 1000; - mediaStartMs = System.currentTimeMillis() - mediaProgressMs; - if (isPlaying) { - if (updateProgressJob == null) { - updateProgressJob = scheduler.scheduleWithFixedDelay(this::updateMediaProgress, 1000, 1000, - TimeUnit.MILLISECONDS); - } - } else { - stopProgressTimer(); - } - } else { - stopProgressTimer(); - mediaProgressMs = 0; - mediaStartMs = 0; - mediaLengthMs = 0; - } - updateMediaProgress(true); - } - - JsonMediaState mediaState = null; - if ("AMAZON_MUSIC".equalsIgnoreCase(musicProviderId) || "TUNEIN".equalsIgnoreCase(musicProviderId)) { - mediaState = connection.getMediaState(device); - } - - // handle music provider id - if (provider != null && isPlaying) { - if (musicProviderId != null) { - this.musicProviderId = musicProviderId; - } - } - - // handle amazon music - String amazonMusicTrackId = ""; - String amazonMusicPlayListId = ""; - boolean amazonMusic = false; - if (mediaState != null) { - String contentId = mediaState.contentId; - if (isPlaying && "CLOUD_PLAYER".equals(mediaState.providerId) && contentId != null - && !contentId.isEmpty()) { - amazonMusicTrackId = contentId; - lastKnownAmazonMusicId = amazonMusicTrackId; - amazonMusic = true; - } + // ignored } // handle bluetooth - String bluetoothMAC = ""; - String bluetoothDeviceName = ""; - boolean bluetoothIsConnected = false; - if (bluetoothState != null) { - for (PairedDevice paired : bluetoothState.getPairedDeviceList()) { + String bluetoothMAC = ""; + String bluetoothDeviceName = ""; + boolean bluetoothIsConnected = false; + for (BluetoothPairedDeviceTO paired : bluetoothState.pairedDeviceList) { String pairedAddress = paired.address; if (paired.connected && pairedAddress != null) { bluetoothIsConnected = true; @@ -1039,153 +883,12 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, } } createBluetoothMACStateDescription(bluetoothState); - } - - // handle radio - boolean isRadio = false; - String radioStationId = ""; - if (mediaState != null) { - radioStationId = Objects.requireNonNullElse(mediaState.radioStationId, ""); - if (!radioStationId.isEmpty()) { - lastKnownRadioStationId = radioStationId; - if ("TUNEIN".equalsIgnoreCase(musicProviderId)) { - isRadio = true; - if (!"PLAYING".equals(mediaState.currentState)) { - radioStationId = ""; - } - } - } - } - - // handle title, subtitle, imageUrl - String title = ""; - String subTitle1 = ""; - String subTitle2 = ""; - String imageUrl = ""; - if (infoText != null) { - if (infoText.title != null) { - title = infoText.title; - } - if (infoText.subText1 != null) { - subTitle1 = infoText.subText1; - } - - if (infoText.subText2 != null) { - subTitle2 = infoText.subText2; - } - } - if (mainArt != null) { - if (mainArt.url != null) { - imageUrl = mainArt.url; - } - } - if (mediaState != null) { - List queueEntries = Objects.requireNonNullElse(mediaState.queue, List.of()); - if (!queueEntries.isEmpty()) { - QueueEntry entry = queueEntries.get(0); - if (isRadio) { - if ((imageUrl == null || imageUrl.isEmpty()) && entry.imageURL != null) { - imageUrl = entry.imageURL; - } - if ((subTitle1 == null || subTitle1.isEmpty()) && entry.radioStationSlogan != null) { - subTitle1 = entry.radioStationSlogan; - } - if ((subTitle2 == null || subTitle2.isEmpty()) && entry.radioStationLocation != null) { - subTitle2 = entry.radioStationLocation; - } - } - } - } - - // handle provider - String providerDisplayName = ""; - if (provider != null) { - if (provider.providerDisplayName != null) { - providerDisplayName = Objects.requireNonNullElse(provider.providerDisplayName, providerDisplayName); - } - String providerName = provider.providerName; - if (providerName != null && !providerName.isEmpty() && providerDisplayName.isEmpty()) { - providerDisplayName = provider.providerName; - } - } - - // handle volume - Integer volume = null; - if (!connection.isSequenceNodeQueueRunning()) { - if (mediaState != null) { - volume = mediaState.volume; - } - if (playerInfo != null && volume == null) { - Volume volumnInfo = playerInfo.volume; - if (volumnInfo != null) { - volume = volumnInfo.volume; - } - } - if (volume != null && volume > 0) { - lastKnownVolume = volume; - } - if (volume == null) { - volume = lastKnownVolume; - } - } - // Update states - if (updateRemind && currentNotifcationUpdateTimer == null) { - updateRemind = false; - updateState(CHANNEL_REMIND, StringType.EMPTY); - } - if (updateRoutine) { - updateRoutine = false; - updateState(CHANNEL_START_ROUTINE, StringType.EMPTY); - } - if (updateTextToSpeech) { - updateTextToSpeech = false; - updateState(CHANNEL_TEXT_TO_SPEECH, StringType.EMPTY); - } - if (updateTextCommand) { - updateTextCommand = false; - updateState(CHANNEL_TEXT_COMMAND, StringType.EMPTY); - } - if (updatePlayMusicVoiceCommand) { - updatePlayMusicVoiceCommand = false; - updateState(CHANNEL_PLAY_MUSIC_VOICE_COMMAND, StringType.EMPTY); - } - - updateState(CHANNEL_MUSIC_PROVIDER_ID, new StringType(musicProviderId)); - updateState(CHANNEL_AMAZON_MUSIC_TRACK_ID, new StringType(amazonMusicTrackId)); - updateState(CHANNEL_AMAZON_MUSIC, isPlaying && amazonMusic ? OnOffType.ON : OnOffType.OFF); - updateState(CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID, new StringType(amazonMusicPlayListId)); - updateState(CHANNEL_RADIO_STATION_ID, new StringType(radioStationId)); - updateState(CHANNEL_RADIO, isPlaying && isRadio ? OnOffType.ON : OnOffType.OFF); - updateState(CHANNEL_PROVIDER_DISPLAY_NAME, new StringType(providerDisplayName)); - updateState(CHANNEL_PLAYER, isPlaying ? PlayPauseType.PLAY : PlayPauseType.PAUSE); - updateState(CHANNEL_IMAGE_URL, new StringType(imageUrl)); - updateState(CHANNEL_TITLE, new StringType(title)); - if (volume != null) { - updateState(CHANNEL_VOLUME, new PercentType(volume)); - } - updateState(CHANNEL_SUBTITLE1, new StringType(subTitle1)); - updateState(CHANNEL_SUBTITLE2, new StringType(subTitle2)); - if (bluetoothState != null) { updateState(CHANNEL_BLUETOOTH, bluetoothIsConnected ? OnOffType.ON : OnOffType.OFF); updateState(CHANNEL_BLUETOOTH_MAC, new StringType(bluetoothMAC)); updateState(CHANNEL_BLUETOOTH_DEVICE_NAME, new StringType(bluetoothDeviceName)); } - - updateState(CHANNEL_ASCENDING_ALARM, - ascendingAlarm != null ? (ascendingAlarm ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF); - - updateState(CHANNEL_DO_NOT_DISTURB, - doNotDisturb != null ? (doNotDisturb ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF); - - final Integer notificationVolumeLevel = this.notificationVolumeLevel; - if (notificationVolumeLevel != null) { - updateState(CHANNEL_NOTIFICATION_VOLUME, new PercentType(notificationVolumeLevel)); - } else { - updateState(CHANNEL_NOTIFICATION_VOLUME, UnDefType.UNDEF); - } - } catch (Exception e) { + } catch (RuntimeException e) { this.logger.debug("Handle updateState {} failed: {}", this.getThing().getUID(), e.getMessage(), e); - disableUpdate = false; } } @@ -1195,48 +898,24 @@ private void updateEqualizerState() { return; } - Connection connection = findConnection(); - if (connection == null) { - return; - } - Device device = findDevice(); + DeviceTO device = this.device; if (device == null) { return; + } - Integer bass = null; - Integer midrange = null; - Integer treble = null; - try { - JsonEqualizer equalizer = connection.getEqualizer(device); - if (equalizer != null) { - bass = equalizer.bass; - midrange = equalizer.mid; - treble = equalizer.treble; - } - this.lastKnownEqualizer = equalizer; - } catch (ConnectionException e) { - logger.debug("Failed to get equalizer"); - return; - } - if (bass != null) { - updateState(CHANNEL_EQUALIZER_BASS, new DecimalType(bass)); - } - if (midrange != null) { - updateState(CHANNEL_EQUALIZER_MIDRANGE, new DecimalType(midrange)); - } - if (treble != null) { - updateState(CHANNEL_EQUALIZER_TREBLE, new DecimalType(treble)); - } - } - private void updateMediaProgress() { - updateMediaProgress(false); + findConnection().flatMap(connection -> connection.getEqualizer(device)).ifPresent(equalizer -> { + updateState(CHANNEL_EQUALIZER_BASS, new DecimalType(equalizer.bass)); + updateState(CHANNEL_EQUALIZER_MIDRANGE, new DecimalType(equalizer.mid)); + updateState(CHANNEL_EQUALIZER_TREBLE, new DecimalType(equalizer.treble)); + this.lastKnownEqualizer = equalizer; + }); } private void updateMediaProgress(boolean updateMediaLength) { synchronized (progressLock) { - if (mediaStartMs > 0) { - long currentPlayTimeMs = isPlaying ? System.currentTimeMillis() - mediaStartMs : mediaProgressMs; + if (isPlaying && mediaStartMs > 0) { + long currentPlayTimeMs = System.currentTimeMillis() - mediaStartMs; if (mediaLengthMs > 0) { int progressPercent = (int) Math.min(100, Math.round((double) currentPlayTimeMs / (double) mediaLengthMs * 100)); @@ -1259,70 +938,129 @@ private void updateMediaProgress(boolean updateMediaLength) { } } - public void handlePushActivity(CustomerHistoryRecord customerHistoryRecord) { - List voiceHistoryRecordItems = customerHistoryRecord.voiceHistoryRecordItems; - if (voiceHistoryRecordItems != null) { - for (VoiceHistoryRecordItem voiceHistoryRecordItem : voiceHistoryRecordItems) { - String recordItemType = voiceHistoryRecordItem.recordItemType; - if ("CUSTOMER_TRANSCRIPT".equals(recordItemType) || "ASR_REPLACEMENT_TEXT".equals(recordItemType)) { - String customerTranscript = voiceHistoryRecordItem.transcriptText; - if (customerTranscript != null && !customerTranscript.isEmpty()) { - // REMOVE WAKEWORD - String wakeWordPrefix = this.wakeWord; - if (wakeWordPrefix != null - && customerTranscript.toLowerCase().startsWith(wakeWordPrefix.toLowerCase())) { - customerTranscript = customerTranscript.substring(wakeWordPrefix.length()).trim(); - // STOP IF WAKEWORD ONLY - if (customerTranscript.isEmpty()) { - return; - } + public synchronized void handlePushActivity(CustomerHistoryRecordTO customerHistoryRecord) { + long recordTimestamp = customerHistoryRecord.timestamp; + if (recordTimestamp <= lastCustomerHistoryRecordTimestamp) { + return; + } + lastCustomerHistoryRecordTimestamp = recordTimestamp; + List voiceHistoryRecordItems = customerHistoryRecord.voiceHistoryRecordItems; + for (CustomerHistoryRecordVoiceTO voiceHistoryRecordItem : voiceHistoryRecordItems) { + String recordItemType = voiceHistoryRecordItem.recordItemType; + if ("CUSTOMER_TRANSCRIPT".equals(recordItemType) || "ASR_REPLACEMENT_TEXT".equals(recordItemType)) { + String customerTranscript = voiceHistoryRecordItem.transcriptText; + if (!customerTranscript.isEmpty()) { + // REMOVE WAKE WORD + String wakeWordPrefix = this.wakeWord; + if (wakeWordPrefix != null + && customerTranscript.toLowerCase().startsWith(wakeWordPrefix.toLowerCase())) { + customerTranscript = customerTranscript.substring(wakeWordPrefix.length()).trim(); + // STOP IF WAKE WORD ONLY + if (customerTranscript.isEmpty()) { + return; } - updateState(CHANNEL_LAST_VOICE_COMMAND, new StringType(customerTranscript)); - } - } else if ("ALEXA_RESPONSE".equals(recordItemType) || "TTS_REPLACEMENT_TEXT".equals(recordItemType)) { - String alexaResponse = voiceHistoryRecordItem.transcriptText; - if (alexaResponse != null && !alexaResponse.isEmpty()) { - updateState(CHANNEL_LAST_SPOKEN_TEXT, new StringType(alexaResponse)); } + updateState(CHANNEL_LAST_VOICE_COMMAND, new StringType(customerTranscript)); + } + } else if ("ALEXA_RESPONSE".equals(recordItemType) || "TTS_REPLACEMENT_TEXT".equals(recordItemType)) { + String alexaResponse = voiceHistoryRecordItem.transcriptText; + if (alexaResponse != null && !alexaResponse.isEmpty()) { + updateState(CHANNEL_LAST_SPOKEN_TEXT, new StringType(alexaResponse)); } } } } + public void handleNowPlayingUpdated(PlayerStateInfoTO playerState) { + findConnection().ifPresent(connection -> { + if (currentlyPlayingQueueId.equals(playerState.queueId)) { + // update when the queueId is the same + updateMediaPlayerState(playerState, connection.isSequenceNodeQueueRunning(), 1); + } + }); + } + + public void updateMediaSessions() { + findConnection().ifPresent(connection -> { + DeviceTO device = this.device; + if (device == null || !isPlaying) { + return; + } + List mediaSessions = connection.getMediaSessions(device); + for (MediaSessionTO mediaSession : mediaSessions) { + if (findIn(mediaSession.endpointList, e -> e.id.deviceSerialNumber, device.serialNumber).isPresent()) { + updateMediaPlayerState(mediaSession.nowPlayingData, connection.isSequenceNodeQueueRunning(), 1000); + } + } + }); + } + + private void refreshAudioPlayerState() { + findConnection().ifPresent(connection -> { + try { + DeviceTO device = this.device; + if (device != null) { + PlayerStateTO playerState = connection.getPlayerState(device); + updateMediaPlayerState(playerState.playerInfo, connection.isSequenceNodeQueueRunning(), 1000); + } + } catch (ConnectionException ignored) { + } + }); + } + public void handlePushCommand(String command, String payload) { this.logger.debug("Handle push command {}", command); + Connection connection = this.findConnection().orElse(null); + switch (command) { case "PUSH_VOLUME_CHANGE": - JsonCommandPayloadPushVolumeChange volumeChange = Objects - .requireNonNull(gson.fromJson(payload, JsonCommandPayloadPushVolumeChange.class)); - Connection connection = this.findConnection(); - Integer volumeSetting = volumeChange.volumeSetting; - Boolean muted = volumeChange.isMuted; - if (muted != null && muted) { + PushVolumeChangeTO volumeChange = Objects + .requireNonNull(gson.fromJson(payload, PushVolumeChangeTO.class)); + + if (volumeChange.isMuted) { updateState(CHANNEL_VOLUME, new PercentType(0)); } - if (volumeSetting != null && connection != null && !connection.isSequenceNodeQueueRunning()) { - lastKnownVolume = volumeSetting; + if (connection != null && !connection.isSequenceNodeQueueRunning()) { + lastKnownVolume = volumeChange.volumeSetting; updateState(CHANNEL_VOLUME, new PercentType(lastKnownVolume)); } break; case "PUSH_EQUALIZER_STATE_CHANGE": - updateEqualizerState(); + PushEqualizerStateChangeTO equalizerStateChange = Objects + .requireNonNull(gson.fromJson(payload, PushEqualizerStateChangeTO.class)); + updateState(CHANNEL_EQUALIZER_BASS, new DecimalType(equalizerStateChange.bass)); + updateState(CHANNEL_EQUALIZER_MIDRANGE, new DecimalType(equalizerStateChange.midrange)); + updateState(CHANNEL_EQUALIZER_TREBLE, new DecimalType(equalizerStateChange.treble)); + break; + case "PUSH_AUDIO_PLAYER_STATE": + PushAudioPlayerStateTO audioPlayerState = Objects + .requireNonNull(gson.fromJson(payload, PushAudioPlayerStateTO.class)); + // FINISHED is emitted when the track finished, but the player continues with the next track + // PLAYING is emitted when a track starts (either first nextAlarmTime or next track) + // INTERRUPTED is emitted when the player finally stops + if (audioPlayerState.audioPlayerState == INTERRUPTED + || (!isPlaying && audioPlayerState.audioPlayerState == PLAYING) + || ("SPOTIFY".equals(musicProviderId))) { + // we only need to update the state when the player stops or starts, not on track changes + // except for spotify + refreshAudioPlayerState(); + } + break; + case "PUSH_MEDIA_QUEUE_CHANGE": + // update the media state with a request to get the new queue id + refreshAudioPlayerState(); break; default: - AccountHandler account = this.account; - Device device = this.device; - if (account != null && device != null) { + DeviceTO device = this.device; + if (device != null) { this.disableUpdate = false; - updateState(account, device, null, null, null, null, null, null, null); + updateState(device, null, null, null, null, null); } } } - public void updateNotifications(ZonedDateTime currentTime, ZonedDateTime now, - @Nullable JsonCommandPayloadPushNotificationChange pushPayload, - List notifications) { - Device device = this.device; + public void updateNotifications(List notifications) { + DeviceTO device = this.device; if (device == null) { return; } @@ -1331,53 +1069,30 @@ public void updateNotifications(ZonedDateTime currentTime, ZonedDateTime now, ZonedDateTime nextAlarm = null; ZonedDateTime nextMusicAlarm = null; ZonedDateTime nextTimer = null; - for (JsonNotificationResponse notification : notifications) { - if (Objects.equals(notification.deviceSerialNumber, device.serialNumber)) { - // notification for this device - if ("ON".equals(notification.status)) { - if ("Reminder".equals(notification.type)) { - String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString(); - String date = notification.originalDate != null ? notification.originalDate - : ZonedDateTime.now().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE); - String time = notification.originalTime != null ? notification.originalTime : "00:00:00"; - ZonedDateTime alarmTime = ZonedDateTime.parse(date + "T" + time + offset, - DateTimeFormatter.ISO_DATE_TIME); - String recurringPattern = notification.recurringPattern; - if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) { - continue; // Ignore recurring entry if alarm time is before now - } - if (nextReminder == null || alarmTime.isBefore(nextReminder)) { - nextReminder = alarmTime; - } - } else if ("Timer".equals(notification.type)) { - // use remaining time - ZonedDateTime alarmTime = currentTime.plus(notification.remainingTime, ChronoUnit.MILLIS); - if (nextTimer == null || alarmTime.isBefore(nextTimer)) { - nextTimer = alarmTime; + for (Notification notification : notifications) { + if (Objects.equals(notification.deviceSerial(), device.serialNumber)) { + switch (notification.type()) { + case "Reminder": + if (nextReminder == null || notification.nextAlarmTime().isBefore(nextReminder)) { + nextReminder = notification.nextAlarmTime(); } - } else if ("Alarm".equals(notification.type)) { - String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString(); - ZonedDateTime alarmTime = ZonedDateTime - .parse(notification.originalDate + "T" + notification.originalTime + offset); - String recurringPattern = notification.recurringPattern; - if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) { - continue; // Ignore recurring entry if alarm time is before now - } - if (nextAlarm == null || alarmTime.isBefore(nextAlarm)) { - nextAlarm = alarmTime; + break; + case "Timer": + if (nextTimer == null || notification.nextAlarmTime().isBefore(nextTimer)) { + nextTimer = notification.nextAlarmTime(); } - } else if ("MusicAlarm".equals(notification.type)) { - String offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()).toString(); - ZonedDateTime alarmTime = ZonedDateTime - .parse(notification.originalDate + "T" + notification.originalTime + offset); - String recurringPattern = notification.recurringPattern; - if (recurringPattern != null && !recurringPattern.isBlank() && alarmTime.isBefore(now)) { - continue; // Ignore recurring entry if alarm time is before now + break; + case "Alarm": + if (nextAlarm == null || notification.nextAlarmTime().isBefore(nextAlarm)) { + nextAlarm = notification.nextAlarmTime(); } - if (nextMusicAlarm == null || alarmTime.isBefore(nextMusicAlarm)) { - nextMusicAlarm = alarmTime; + break; + case "MusicAlarm": + if (nextMusicAlarm == null || notification.nextAlarmTime().isBefore(nextMusicAlarm)) { + nextMusicAlarm = notification.nextAlarmTime(); } - } + break; + default: } } } @@ -1389,11 +1104,6 @@ public void updateNotifications(ZonedDateTime currentTime, ZonedDateTime now, updateState(CHANNEL_NEXT_TIMER, nextTimer == null ? UnDefType.UNDEF : new DateTimeType(nextTimer)); } - @Override - public void updateChannelState(String channelId, State state) { - updateState(channelId, state); - } - @Override protected void updateState(String channelId, State state) { stateCache.put(channelId, () -> state); diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/FlashBriefingProfileHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/FlashBriefingProfileHandler.java index 8f4e1eb0f4..fa5958b162 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/FlashBriefingProfileHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/FlashBriefingProfileHandler.java @@ -13,13 +13,12 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.handler; +import static org.eclipse.jetty.util.StringUtil.isBlank; import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.concurrent.ScheduledFuture; +import java.util.Objects; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -31,15 +30,19 @@ import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; -import org.openhab.core.types.CommandOption; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; -import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; +import org.smarthomej.binding.amazonechocontrol.internal.dto.DeviceTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.EnabledFeedTO; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; /** * The {@link FlashBriefingProfileHandler} is responsible for storing and loading of a flash briefing configuration @@ -48,53 +51,72 @@ */ @NonNullByDefault public class FlashBriefingProfileHandler extends BaseThingHandler { + @SuppressWarnings("unchecked") + public static final TypeToken> LIST_TYPE_TOKEN = (TypeToken>) TypeToken + .getParameterized(List.class, EnabledFeedTO.class); + public static final String ENABLED_FEEDS = "enabledFeeds"; + private final Logger logger = LoggerFactory.getLogger(FlashBriefingProfileHandler.class); - private final SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider; + private final Gson gson; - @Nullable - AccountHandler accountHandler; - Storage stateStorage; - boolean updatePlayOnDevice = true; - String currentConfigurationJson = ""; - private @Nullable ScheduledFuture updateStateJob; + private @Nullable AccountHandler accountHandler; + private final Storage stateStorage; + private List ourFeeds = List.of(); - public FlashBriefingProfileHandler(Thing thing, Storage storage, - SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider) { + public FlashBriefingProfileHandler(Thing thing, Storage storage, Gson gson) { super(thing); - this.dynamicCommandDescriptionProvider = dynamicCommandDescriptionProvider; this.stateStorage = storage; - } - - public @Nullable AccountHandler findAccountHandler() { - return this.accountHandler; + this.gson = gson; } @Override public void initialize() { - updatePlayOnDevice = true; - logger.info("{} initialized", getClass().getSimpleName()); - if (!this.currentConfigurationJson.isEmpty()) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.UNKNOWN); - Bridge bridge = this.getBridge(); - if (bridge != null) { - AccountHandler account = (AccountHandler) bridge.getHandler(); - if (account != null) { - account.addFlashBriefingProfileHandler(this); - } + @SuppressWarnings("unchecked") + List restoredFeeds = (List) stateStorage.get(ENABLED_FEEDS); + if (restoredFeeds == null) { + // convert from old + String configurationJson = (String) stateStorage.get("configurationJson"); + if (!isBlank(configurationJson)) { + restoredFeeds = Objects.requireNonNullElse(gson.fromJson(configurationJson, LIST_TYPE_TOKEN), + List.of()); + stateStorage.put("enabledFeeds", restoredFeeds); + } else { + restoredFeeds = List.of(); } } + + if (restoredFeeds.isEmpty()) { + saveCurrentProfile(); + } else { + ourFeeds = restoredFeeds; + } + + Bridge bridge = this.getBridge(); + if (bridge != null) { + accountHandler = (AccountHandler) bridge.getHandler(); + } + if (accountHandler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, "Bridge handler not found."); + return; + } + + AccountHandler handler = this.accountHandler; + if (handler != null) { + bridgeStatusChanged(handler.getThing().getStatusInfo()); + } } @Override - public void dispose() { - ScheduledFuture updateStateJob = this.updateStateJob; - this.updateStateJob = null; - if (updateStateJob != null) { - updateStateJob.cancel(false); + public void bridgeStatusChanged(ThingStatusInfo bridgeStatus) { + if (bridgeStatus.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + return; + } + if (ourFeeds.isEmpty()) { + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Waiting for feed configuration"); + } else { + updateStatus(ThingStatus.ONLINE); } - super.dispose(); } @Override @@ -103,121 +125,63 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (accountHandler == null) { return; } - int waitForUpdate = -1; - ScheduledFuture updateStateJob = this.updateStateJob; - this.updateStateJob = null; - if (updateStateJob != null) { - updateStateJob.cancel(false); - } String channelId = channelUID.getId(); if (command instanceof RefreshType) { - waitForUpdate = 0; - } - if (channelId.equals(CHANNEL_SAVE)) { - if (command.equals(OnOffType.ON)) { - saveCurrentProfile(accountHandler); - waitForUpdate = 500; + accountHandler.updateFlashBriefingHandlers(); + } else if (channelId.equals(CHANNEL_SAVE) && command.equals(OnOffType.ON)) { + saveCurrentProfile(); + accountHandler.updateFlashBriefingHandlers(); + } else if (channelId.equals(CHANNEL_ACTIVE) && command.equals(OnOffType.ON)) { + if (!ourFeeds.isEmpty()) { + accountHandler.setEnabledFlashBriefing(ourFeeds); } - } - if (channelId.equals(CHANNEL_ACTIVE)) { - if (command.equals(OnOffType.ON)) { - String currentConfigurationJson = this.currentConfigurationJson; - if (!currentConfigurationJson.isEmpty()) { - accountHandler.setEnabledFlashBriefingsJson(currentConfigurationJson); - updateState(CHANNEL_ACTIVE, OnOffType.ON); - waitForUpdate = 500; - } - } - } - if (channelId.equals(CHANNEL_PLAY_ON_DEVICE)) { - if (command instanceof StringType) { - String deviceSerialOrName = command.toFullString(); - String currentConfigurationJson = this.currentConfigurationJson; - if (!currentConfigurationJson.isEmpty()) { - String old = accountHandler.getEnabledFlashBriefingsJson(); - accountHandler.setEnabledFlashBriefingsJson(currentConfigurationJson); - Device device = accountHandler.findDeviceJsonBySerialOrName(deviceSerialOrName); - if (device == null) { - logger.warn("Device '{}' not found", deviceSerialOrName); - } else { - @Nullable - Connection connection = accountHandler.findConnection(); - if (connection == null) { - logger.warn("Connection for '{}' not found", accountHandler.getThing().getUID().getId()); - } else { - connection.executeSequenceCommand(device, "Alexa.FlashBriefing.Play", Map.of()); - - scheduler.schedule(() -> accountHandler.setEnabledFlashBriefingsJson(old), 1000, - TimeUnit.MILLISECONDS); - - updateState(CHANNEL_ACTIVE, OnOffType.ON); - } - } - updatePlayOnDevice = true; - waitForUpdate = 1000; + } else if (channelId.equals(CHANNEL_PLAY_ON_DEVICE) && command instanceof StringType) { + String deviceSerialOrName = command.toFullString(); + if (!ourFeeds.isEmpty()) { + DeviceTO device = accountHandler.findDeviceBySerialOrName(deviceSerialOrName); + if (device == null) { + logger.warn("Device '{}' not found", deviceSerialOrName); + return; } - } - } - if (waitForUpdate >= 0) { - this.updateStateJob = scheduler.schedule(() -> accountHandler.updateFlashBriefingHandlers(), waitForUpdate, - TimeUnit.MILLISECONDS); - } - } - public boolean initialize(AccountHandler handler, String currentConfigurationJson) { - updateState(CHANNEL_SAVE, OnOffType.OFF); - if (updatePlayOnDevice) { - updateState(CHANNEL_PLAY_ON_DEVICE, new StringType("")); - } - if (!handler.equals(this.accountHandler)) { - this.accountHandler = handler; - String configurationJson = this.stateStorage.get("configurationJson"); - if (configurationJson == null || configurationJson.isEmpty()) { - this.currentConfigurationJson = saveCurrentProfile(handler); - } else { - this.currentConfigurationJson = configurationJson; - } - if (!this.currentConfigurationJson.isEmpty()) { - updateStatus(ThingStatus.ONLINE); + List oldEnabledFeeds = accountHandler.getEnabledFlashBriefings(); + accountHandler.setEnabledFlashBriefing(ourFeeds); - } else { - updateStatus(ThingStatus.UNKNOWN); + Connection connection = accountHandler.getConnection(); + if (!connection.isLoggedIn()) { + logger.warn("Can't execute command when account is logged out."); + } else { + connection.executeSequenceCommand(device, "Alexa.FlashBriefing.Play", Map.of()); + scheduler.schedule(() -> accountHandler.setEnabledFlashBriefing(oldEnabledFeeds), 1000, + TimeUnit.MILLISECONDS); + } } } - if (this.currentConfigurationJson.equals(currentConfigurationJson)) { - updateState(CHANNEL_ACTIVE, OnOffType.ON); - } else { - updateState(CHANNEL_ACTIVE, OnOffType.OFF); - } - return this.currentConfigurationJson.equals(currentConfigurationJson); } - private String saveCurrentProfile(AccountHandler connection) { - String configurationJson = ""; - configurationJson = connection.getEnabledFlashBriefingsJson(); - this.currentConfigurationJson = configurationJson; - if (!configurationJson.isEmpty()) { - this.stateStorage.put("configurationJson", configurationJson); - } - return configurationJson; + /** + * Update the current profile's state and check if it is the same + * + * @param configurationJson a flash briefing profile configuration + * @return {@code true} if the provided feed configuration is the same as this thing's configuration + */ + public boolean updateAndCheck(List configurationJson) { + boolean isSame = ourFeeds.equals(configurationJson); + updateState(CHANNEL_ACTIVE, OnOffType.from(isSame)); + return isSame; } - public void setCommandDescription(Collection devices) { - if (devices.isEmpty()) { - return; - } - - List options = new ArrayList<>(); - options.add(new CommandOption("", "")); - for (Device device : devices) { - final String value = device.serialNumber; - if (value != null && device.getCapabilities().contains("FLASH_BRIEFING")) { - options.add(new CommandOption(value, device.accountName)); + private void saveCurrentProfile() { + AccountHandler accountHandler = this.accountHandler; + if (accountHandler == null) { + ourFeeds = List.of(); + } else { + List newFeeds = accountHandler.getEnabledFlashBriefings(); + if (!newFeeds.isEmpty()) { + ourFeeds = newFeeds; + stateStorage.put(ENABLED_FEEDS, newFeeds); } } - - ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_PLAY_ON_DEVICE); - dynamicCommandDescriptionProvider.setCommandOptions(channelUID, options); } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java index e58b77d0f9..256ad2553b 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java @@ -47,13 +47,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentifiers; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentity; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeTags; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroupIdentifiers; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroupIdentity; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroups.SmartHomeGroup; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeTags; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice; import org.smarthomej.binding.amazonechocontrol.internal.smarthome.ChannelInfo; import org.smarthomej.binding.amazonechocontrol.internal.smarthome.Constants; import org.smarthomej.binding.amazonechocontrol.internal.smarthome.InterfaceHandler; @@ -188,11 +188,6 @@ public void initialize() { @Override public void dispose() { - AccountHandler accountHandler = getAccountHandler(); - if (accountHandler != null) { - accountHandler.removeSmartHomeDeviceHandler(this); - } - dynamicCommandDescriptionProvider.removeCommandDescriptionForThing(thing.getUID()); dynamicStateDescriptionProvider.removeDescriptionsForThing(thing.getUID()); } @@ -246,7 +241,7 @@ public void updateChannelStates(List allDevices, } JsonArray states = applianceIdToCapabilityStates.getOrDefault(applianceId, lastStates.getOrDefault(applianceId, new JsonArray())); - if (states.size() == 0) { + if (states.isEmpty()) { logger.trace("No states array found for applianceId={}.", applianceId); continue; } @@ -323,8 +318,8 @@ public void handleCommand(ChannelUID channelUID, Command command) { logger.debug("accountHandler is null in {}", thing.getUID()); return; } - Connection connection = accountHandler.findConnection(); - if (connection == null) { + Connection connection = accountHandler.getConnection(); + if (!connection.isLoggedIn()) { logger.debug("connection is null in {}", thing.getUID()); return; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonActivities.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonActivities.java deleted file mode 100644 index 67097743d8..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonActivities.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link JsonActivities} encapsulate the GSON data of the push command for push activity - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonActivities { - - public @Nullable List activities; - - public static class Activity { - public @Nullable String activityStatus; - public @Nullable Long creationTimestamp; - public @Nullable String description; - public @Nullable Object domainAttributes; - public @Nullable Object domainType; - public @Nullable Object feedbackAttributes; - public @Nullable String id; - public @Nullable String intentType; - public @Nullable String providerInfoDescription; - public @Nullable String registeredCustomerId; - public @Nullable Object sourceActiveUsers; - public @Nullable List sourceDeviceIds; - public @Nullable String utteranceId; - public @Nullable Long version; - - public List getSourceDeviceIds() { - return Objects.requireNonNullElse(sourceDeviceIds, List.of()); - } - - public static class SourceDeviceId { - public @Nullable String deviceAccountId; - public @Nullable String deviceType; - public @Nullable String serialNumber; - } - - public static class Description { - public @Nullable String summary; - public @Nullable String firstUtteranceId; - public @Nullable String firstStreamId; - } - - public Description parseDescription() { - String description = this.description; - if (description == null || description.isEmpty() || !description.startsWith("{") - || !description.endsWith("}")) { - return new Description(); - } - Gson gson = new Gson(); - try { - Description description1 = gson.fromJson(description, Description.class); - return description1 != null ? description1 : new Description(); - } catch (JsonSyntaxException e) { - return new Description(); - } - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementContent.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementContent.java deleted file mode 100644 index d57ff19c4b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementContent.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.connection.AnnouncementWrapper; - -/** - * The {@link JsonAnnouncementContent} encapsulate the GSON data of the sequence command AlexaAnnouncement for sending - * announcements - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonAnnouncementContent { - public String locale = ""; - public Display display; - public Speak speak; - - public JsonAnnouncementContent(AnnouncementWrapper announcement) { - display = new Display(announcement.getBodyText(), announcement.getTitle()); - speak = new Speak(announcement.getSpeak()); - } - - public static class Display { - public String title; - public String body; - - public Display(String body, @Nullable String title) { - this.body = body; - this.title = (title == null || title.isEmpty() ? "openHAB" : title); - } - } - - public static class Speak { - public String type; - public String value; - - public Speak(String speakText) { - type = (speakText.startsWith("") && speakText.endsWith("")) ? "ssml" : "text"; - value = speakText; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java deleted file mode 100644 index 5666feb7bb..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonAnnouncementTarget} encapsulate the GSON data of the sequence command AlexaAnnouncement for - * announcement target - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonAnnouncementTarget { - - public @Nullable String customerId; - public List devices; - - public JsonAnnouncementTarget(List deviceList) { - customerId = deviceList.get(0).deviceOwnerCustomerId; - devices = deviceList.stream().map(TargetDevice::new).collect(Collectors.toList()); - } - - public static class TargetDevice { - public @Nullable String deviceSerialNumber; - public @Nullable String deviceTypeId; - - public TargetDevice(JsonDevices.Device device) { - deviceSerialNumber = device.serialNumber; - deviceTypeId = device.deviceType; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java deleted file mode 100644 index 4994b32b36..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonAscendingAlarm} encapsulate the GSON data of the /api/ascending-alarm response - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonAscendingAlarm { - - public @Nullable List ascendingAlarmModelList; - - public static class AscendingAlarmModel { - public @Nullable Boolean ascendingAlarmEnabled; - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAutomation.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAutomation.java deleted file mode 100644 index 675892e03f..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonAutomation.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; -import java.util.TreeMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonAutomation} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonAutomation { - public @Nullable String automationId; - public @Nullable String name; - public @Nullable List triggers; - public @Nullable TreeMap sequence; - public @Nullable String status; - public long creationTimeEpochMillis; - public long lastUpdatedTimeEpochMillis; - - public static class Trigger { - public @Nullable Payload payload; - public @Nullable String id; - public @Nullable String type; - } - - public static class Payload { - public @Nullable String customerId; - public @Nullable String utterance; - public @Nullable String locale; - public @Nullable String marketplaceId; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBluetoothStates.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBluetoothStates.java deleted file mode 100644 index 9b7ecb9e64..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBluetoothStates.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonDevices.Device; - -/** - * The {@link JsonBluetoothStates} encapsulate the GSON data of bluetooth state - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonBluetoothStates { - - public @Nullable BluetoothState findStateByDevice(@Nullable Device device) { - if (device == null) { - return null; - } - @Nullable - BluetoothState @Nullable [] bluetoothStates = this.bluetoothStates; - if (bluetoothStates == null) { - return null; - } - for (BluetoothState state : bluetoothStates) { - if (state != null && Objects.equals(state.deviceSerialNumber, device.serialNumber)) { - return state; - } - } - return null; - } - - public BluetoothState @Nullable [] bluetoothStates; - - public static class PairedDevice { - public @Nullable String address; - public boolean connected; - public @Nullable String deviceClass; - public @Nullable String friendlyName; - public @Nullable List profiles; - } - - public static class BluetoothState { - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - public @Nullable String friendlyName; - public boolean gadgetPaired; - public boolean online; - public @Nullable List pairedDeviceList; - - public List getPairedDeviceList() { - return Objects.requireNonNullElse(pairedDeviceList, List.of()); - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java deleted file mode 100644 index 1ba949e887..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonBootstrapResult.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.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.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java deleted file mode 100644 index b119b96e93..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * @author Lukas Knoeller - Initial contribution - */ -@NonNullByDefault -public class JsonColorTemperature { - public @Nullable String temperatureName; - - public JsonColorTemperature(String temperature) { - temperatureName = temperature; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColors.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColors.java deleted file mode 100644 index 7b9a3b4e99..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonColors.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * @author Lukas Knoeller - Initial contribution - */ -@NonNullByDefault -public class JsonColors { - public @Nullable String colorName; - - public JsonColors(String colorName) { - this.colorName = colorName; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java deleted file mode 100644 index 3d36e2da70..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushActivity} encapsulate the GSON data of the push command with device information - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushActivity { - - public @Nullable String destinationUserId; - public @Nullable Long timestamp; - - public @Nullable Key key; - - public static class Key { - public @Nullable String entryId; - public @Nullable String registeredUserId; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushAudioPlayerState.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushAudioPlayerState.java deleted file mode 100644 index c2f9a3eef6..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushAudioPlayerState.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushAudioPlayerState} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushAudioPlayerState extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String mediaReferenceId; - public @Nullable Boolean error; - public @Nullable String audioPlayerState; - public @Nullable String errorMessage; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushBluetoothStateChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushBluetoothStateChange.java deleted file mode 100644 index 153d60bedb..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushBluetoothStateChange.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushBluetoothStateChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushBluetoothStateChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String bluetoothEvent; - public @Nullable String bluetoothEventPayload; - public @Nullable Boolean bluetoothEventSuccess; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushContentFocusChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushContentFocusChange.java deleted file mode 100644 index ab2a1d3b62..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushContentFocusChange.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushContentFocusChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushContentFocusChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String clientId; - public @Nullable String deviceComponent; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java deleted file mode 100644 index eb7985e7e2..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushDevice} encapsulate the GSON data of the push command with device information - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushDevice { - - public @Nullable DopplerId dopplerId; - - public static class DopplerId { - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDopllerConnectionChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDopllerConnectionChange.java deleted file mode 100644 index 660cc93902..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDopllerConnectionChange.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushDopllerConnectionChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushDopllerConnectionChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String dopplerConnectionState; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushEqualizerChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushEqualizerChange.java deleted file mode 100644 index bdb041667a..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushEqualizerChange.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushEqualizerChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushEqualizerChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable Integer bass; - public @Nullable Integer treble; - public @Nullable Integer midrange; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaChange.java deleted file mode 100644 index 6b08c3da8b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaChange.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushMediaChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushMediaChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String mediaReferenceId; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaQueueChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaQueueChange.java deleted file mode 100644 index ed1cfb287b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushMediaQueueChange.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushMediaQueueChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushMediaQueueChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String changeType; - public @Nullable String playBackOrder; - public @Nullable Boolean trackOrderChanged; - public @Nullable Object loopMode; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushNotificationChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushNotificationChange.java deleted file mode 100644 index 82c9389381..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushNotificationChange.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushNotificationChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushNotificationChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable String eventType; - public @Nullable String notificationId; - public @Nullable Integer notificationVersion; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushVolumeChange.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushVolumeChange.java deleted file mode 100644 index 1e9caeed49..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushVolumeChange.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCommandPayloadPushVolumeChange} encapsulate the GSON data of automation query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonCommandPayloadPushVolumeChange extends JsonCommandPayloadPushDevice { - public @Nullable String destinationUserId; - public @Nullable Boolean isMuted; - public @Nullable Integer volumeSetting; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCustomerHistoryRecords.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCustomerHistoryRecords.java deleted file mode 100644 index 1e99c9c511..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonCustomerHistoryRecords.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonCustomerHistoryRecords} encapsulate the GSON data of the push command for push customer history record - * - * @author Tom Blum - Initial contribution - */ -@NonNullByDefault -public class JsonCustomerHistoryRecords { - - public @Nullable List customerHistoryRecords; - - public static class CustomerHistoryRecord { - public @Nullable String recordKey; - public @Nullable String recordType; - public @Nullable Long timestamp; - public @Nullable String customerId; - public @Nullable Object device; - public @Nullable Boolean isBinaryFeedbackProvided; - public @Nullable Boolean isFeedbackPositive; - public @Nullable String utteranceType; - public @Nullable String domain; - public @Nullable String intent; - public @Nullable String skillName; - public @Nullable List voiceHistoryRecordItems; - public @Nullable List personsInfo; - - public static class VoiceHistoryRecordItem { - public @Nullable String recordItemKey; - public @Nullable String recordItemType; - public @Nullable String utteranceId; - public @Nullable Long timestamp; - public @Nullable String transcriptText; - public @Nullable String agentVisualName; - public @Nullable List personsInfo; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java deleted file mode 100644 index 44f5ec5ec3..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonDeviceNotificationState} encapsulate the GSON data of the /api/device-notification-state response - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonDeviceNotificationState { - - public @Nullable List deviceNotificationStates; - - public static class DeviceNotificationState { - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - public @Nullable String softwareVersion; - public @Nullable Integer volumeLevel; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDevices.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDevices.java deleted file mode 100644 index 15f79eddf2..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDevices.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonDevices} encapsulate the GSON data of device list - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonDevices { - - public static class Device { - public @Nullable String accountName; - public @Nullable String serialNumber; - public @Nullable String deviceOwnerCustomerId; - public @Nullable String deviceAccountId; - public @Nullable String deviceFamily; - public @Nullable String deviceType; - public @Nullable String softwareVersion; - public boolean online; - public @Nullable Set capabilities; - - public Set getCapabilities() { - return Objects.requireNonNullElse(capabilities, Set.of()); - } - - @Override - public String toString() { - return "Device{" + "accountName='" + accountName + '\'' + ", serialNumber='" + serialNumber + '\'' - + ", deviceOwnerCustomerId='" + deviceOwnerCustomerId + '\'' + ", deviceAccountId='" - + deviceAccountId + '\'' + ", deviceFamily='" + deviceFamily + '\'' + ", deviceType='" + deviceType - + '\'' + ", softwareVersion='" + softwareVersion + '\'' + ", online=" + online + ", capabilities=" - + capabilities + '}'; - } - } - - public List devices = List.of(); -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDoNotDisturb.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDoNotDisturb.java deleted file mode 100644 index e93f2940fa..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonDoNotDisturb.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonDoNotDisturb} encapsulate the GSON data of the /api/dnd/device-status-list response - * - * @author Cody Cutrer - Initial contribution - */ -@NonNullByDefault -public class JsonDoNotDisturb { - - public @Nullable List doNotDisturbDeviceStatusList; - - public static class DoNotDisturbDeviceStatus { - public @Nullable Boolean enabled; - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEqualizer.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEqualizer.java deleted file mode 100644 index 7a3433afed..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonEqualizer.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonEqualizer} encapsulate the GSON data of the get equalizer command - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonEqualizer { - public @Nullable Integer bass = 0; - public @Nullable Integer mid = 0; - public @Nullable Integer treble = 0; - - public JsonEqualizer createClone() { - JsonEqualizer result = new JsonEqualizer(); - result.bass = this.bass; - result.mid = this.mid; - result.treble = this.treble; - return result; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java deleted file mode 100644 index e932665752..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link JsonExchangeTokenResponse} encapsulate the GSON response data of the token exchange - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonExchangeTokenResponse { - public @Nullable Response response; - - public static class Response { - public @Nullable Tokens tokens; - } - - public static class Tokens { - public @Nullable Map cookies; - } - - public static class Cookie { - @SerializedName("Path") - public @Nullable String path; - @SerializedName("Secure") - public @Nullable Boolean secure; - @SerializedName("Value") - public @Nullable String value; - @SerializedName("Expires") - public @Nullable String expires; - @SerializedName("HttpOnly") - public @Nullable Boolean httpOnly; - @SerializedName("Name") - public @Nullable String name; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonFeed.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonFeed.java deleted file mode 100644 index 7a0a3f4b20..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonFeed.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonFeed} encapsulate the GSON data of feed - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonFeed { - public @Nullable Object feedId; - public @Nullable String name; - public @Nullable String skillId; - public @Nullable String imageUrl; - - public JsonFeed(@Nullable Object feedId, @Nullable String skillId) { - this.feedId = feedId; - this.skillId = skillId; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMediaState.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMediaState.java deleted file mode 100644 index 85d48e02c5..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMediaState.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonMediaState} encapsulate the GSON data of the current media state - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonMediaState { - - public @Nullable String clientId; - public @Nullable String contentId; - public @Nullable String contentType; - public @Nullable String currentState; - public @Nullable String imageURL; - public boolean isDisliked; - public boolean isLiked; - public boolean looping; - public @Nullable String mediaOwnerCustomerId; - public boolean muted; - public @Nullable String programId; - public int progressSeconds; - public @Nullable String providerId; - public @Nullable List queue; - public @Nullable String queueId; - public @Nullable Integer queueSize; - public @Nullable String radioStationId; - public int radioVariety; - public @Nullable String referenceId; - public @Nullable String service; - public boolean shuffling; - // public long timeLastShuffled; parsing fails with some values, so do not use it - public int volume; - - public static class QueueEntry { - public @Nullable String album; - public @Nullable String albumAsin; - public @Nullable String artist; - public @Nullable String asin; - public @Nullable String cardImageURL; - public @Nullable String contentId; - public @Nullable String contentType; - public int durationSeconds; - public boolean feedbackDisabled; - public @Nullable String historicalId; - public @Nullable String imageURL; - public int index; - public boolean isAd; - public boolean isDisliked; - public boolean isFreeWithPrime; - public boolean isLiked; - public @Nullable String programId; - public @Nullable String programName; - public @Nullable String providerId; - public @Nullable String queueId; - public @Nullable String radioStationCallSign; - public @Nullable String radioStationId; - public @Nullable String radioStationLocation; - public @Nullable String radioStationSlogan; - public @Nullable String referenceId; - public @Nullable String service; - public @Nullable String startTime; - public @Nullable String title; - public @Nullable String trackId; - public @Nullable String trackStatus; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMusicProvider.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMusicProvider.java deleted file mode 100644 index 1485762d60..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonMusicProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonMusicProvider} encapsulate the GSON returned for a music provider - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonMusicProvider { - public @Nullable String displayName; - public List @Nullable [] supportedTriggers; - public @Nullable String icon; - public @Nullable List supportedProperties; - public @Nullable String id; - public @Nullable String availability; - public @Nullable String description; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationRequest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationRequest.java deleted file mode 100644 index 9f8c7bffa1..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonNotificationRequest} encapsulate the GSON data for a notification request - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonNotificationRequest { - public @Nullable String type = "Reminder"; // "Reminder", "Alarm" - public @Nullable String status = "ON"; - public long alarmTime; - public @Nullable String originalTime; - public @Nullable String originalDate; - public @Nullable String timeZoneId; - public @Nullable String reminderIndex; - public @Nullable JsonNotificationSound sound; - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - public @Nullable String recurringPattern; - public @Nullable String reminderLabel; - public boolean isSaveInFlight = true; - public @Nullable String id = "createReminder"; - public boolean isRecurring = false; - public long createdDate; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationResponse.java deleted file mode 100644 index 21e10af8c9..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationResponse.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonNotificationResponse} encapsulate the GSON data for the result of a notification request - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonNotificationResponse { - // This is only a partial definition, see the example JSON below - public long alarmTime; - public long createdDate; - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - public @Nullable String id; - public @Nullable String status; - public @Nullable String type; - public long remainingTime; - public @Nullable String recurringPattern; - public @Nullable String originalDate; - public @Nullable String originalTime; -} - -/* - * Example JSON: - * { - *    "alarmTime":1518864868060, - *    "createdDate":1518864863801, - *    "deviceSerialNumber":"XXXXXXXXXX", - *    "deviceType":"XXXXXXXXXX", - *    "id":"XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX", - *    "musicAlarmId":null, - *    "musicEntity":null, - *    "notificationIndex":"XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX", - *    "originalDate":null, - *    "originalTime":"11:54:28.060", - *    "provider":null, - *    "recurringPattern":null, - *    "remainingTime":0, - *    "reminderLabel":null, - *    "sound":{ - *       "displayName":"Clarity", - *       "folder":null, - *       "id":"system_alerts_melodic_05", - *       "providerId":"ECHO", - *       "sampleUrl":"https://s3.amazonaws.com/deeappservice.prod.notificationtones/system_alerts_melodic_05.mp3" - *    }, - *    "status":"OFF", - *    "timeZoneId":null, - *    "timerLabel":null, - *    "triggerTime":0, - *    "type":"Alarm", - *    "version":"2", - *    "alarmIndex":null, - *    "isSaveInFlight":true - * } - * - */ diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSound.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSound.java deleted file mode 100644 index d117b3bc43..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSound.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonNotificationSound} encapsulate the GSON data for a notification sound - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonNotificationSound { - public @Nullable String displayName; - public @Nullable String folder; - public @Nullable String id = "system_alerts_melodic_01"; - public @Nullable String providerId = "ECHO"; - public @Nullable String sampleUrl; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSounds.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSounds.java deleted file mode 100644 index 75af7a922d..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNotificationSounds.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonNotificationSounds} encapsulate the GSON data for a notification sounds - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonNotificationSounds { - public @Nullable List notificationSounds; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaySearchPhraseOperationPayload.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaySearchPhraseOperationPayload.java deleted file mode 100644 index 928d16e344..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaySearchPhraseOperationPayload.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonPlaySearchPhraseOperationPayload} encapsulate the GSON for validation requests and results - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonPlaySearchPhraseOperationPayload { - public @Nullable String deviceType = "ALEXA_CURRENT_DEVICE_TYPE"; - public @Nullable String deviceSerialNumber = "ALEXA_CURRENT_DSN"; - public @Nullable String locale = "ALEXA_CURRENT_LOCALE"; - public @Nullable String customerId; - public @Nullable String searchPhrase; - public @Nullable String sanitizedSearchPhrase; - public @Nullable String musicProviderId = "ALEXA_CURRENT_DSN"; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayValidationResult.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayValidationResult.java deleted file mode 100644 index bb74d49385..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayValidationResult.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonPlayValidationResult} encapsulate the GSON for validation result - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonPlayValidationResult { - public @Nullable JsonPlaySearchPhraseOperationPayload operationPayload; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayerState.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayerState.java deleted file mode 100644 index 4be4cdc498..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlayerState.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonPlayerState} encapsulate the GSON data of the player state - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonPlayerState { - public @Nullable PlayerInfo playerInfo; - - public static class PlayerInfo { - public @Nullable String state; - public @Nullable InfoText infoText; - public @Nullable InfoText miniInfoText; - public @Nullable Provider provider; - public @Nullable Volume volume; - public @Nullable MainArt mainArt; - - public @Nullable String queueId; - public @Nullable String mediaId; - - public @Nullable Progress progress; - - public static class InfoText { - public boolean multiLineMode; - public @Nullable String subText1; - public @Nullable String subText2; - public @Nullable String title; - } - - public static class Provider { - public @Nullable String providerDisplayName; - public @Nullable String providerName; - } - - public static class Volume { - public boolean muted; - public int volume; - } - - public static class MainArt { - public @Nullable String altText; - public @Nullable String artType; - public @Nullable String contentType; - public @Nullable String url; - } - - public static class Progress { - public @Nullable Boolean allowScrubbing; - public @Nullable Object locationInfo; - public @Nullable Long mediaLength; - public @Nullable Long mediaProgress; - public @Nullable Boolean showTiming; - public @Nullable Boolean visible; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaylists.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaylists.java deleted file mode 100644 index d7d901e730..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonPlaylists.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonPlayerState} encapsulate the GSON data of playlist query - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonPlaylists { - - public @Nullable Map playlists; - - public static class PlayList { - public @Nullable String playlistId; - public @Nullable String title; - public int trackCount; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java deleted file mode 100644 index f802a4053b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link JsonRegisterAppRequest} encapsulate the GSON data of register application request - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonRegisterAppRequest { - - public JsonRegisterAppRequest(String serial, @Nullable String accessToken, String frc, - List webSiteCookies) { - registrationData.deviceSerial = serial; - authData.accessToken = accessToken; - userContextMap.frc = frc; - cookies.webSiteCookies = webSiteCookies; - } - - @SerializedName("requested_extensions") - public String[] requestedExtensions = { "device_info", "customer_info" }; - - public Cookies cookies = new Cookies(); - @SerializedName("registration_data") - public RegistrationData registrationData = new RegistrationData(); - @SerializedName("auth_data") - public AuthData authData = new AuthData(); - @SerializedName("user_context_map") - public UserContextMap userContextMap = new UserContextMap(); - @SerializedName("requested_token_type") - public String[] requestedTokenType = { "bearer", "mac_dms", "website_cookies" }; - - public static class Cookies { - @SerializedName("website_cookies") - public List webSiteCookies = List.of(); - public @Nullable String domain = ".amazon.com"; - } - - public static class RegistrationData { - public String domain = "Device"; - @SerializedName("app_version") - public String appVersion = "2.2.223830.0"; - @SerializedName("device_type") - public String deviceType = "A2IVLV5VM2W81"; - @SerializedName("device_name") - public String deviceName = "%FIRST_NAME%'s%DUPE_STRATEGY_1ST%openHAB Alexa Binding"; - @SerializedName("os_version") - public String osVersion = "11.4.1"; - @SerializedName("device_serial") - public @Nullable String deviceSerial; - @SerializedName("device_model") - public String deviceModel = "iPhone"; - @SerializedName("app_name") - public String appName = "openHAB Alexa Binding"; - @SerializedName("software_version") - public String softwareVersion = "1"; - } - - public static class AuthData { - @SerializedName("access_token") - public @Nullable String accessToken; - } - - public static class UserContextMap { - public String frc = ""; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java deleted file mode 100644 index 717665424c..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link JsonRegisterAppResponse} encapsulate the GSON data of response from the register command - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonRegisterAppResponse { - - public @Nullable Response response; - - @SerializedName("request_id") - public @Nullable String requestId; - - public static class Response { - public @Nullable Success success; - } - - public static class Success { - public @Nullable Extensions extensions; - - public @Nullable Tokens tokens; - - @SerializedName("customer_id") - public @Nullable String customerId; - } - - public static class Extensions { - @SerializedName("device_info") - public @Nullable DeviceInfo deviceInfo; - - @SerializedName("customer_info") - public @Nullable CustomerInfo customerInfo; - - @SerializedName("customer_id") - public @Nullable String customerId; - } - - public static class DeviceInfo { - @SerializedName("device_name") - public @Nullable String deviceName; - - @SerializedName("device_serial_number") - public @Nullable String deviceSerialNumber; - - @SerializedName("device_type") - public @Nullable String deviceType; - } - - public static class CustomerInfo { - @SerializedName("account_pool") - public @Nullable String accountPool; - - @SerializedName("user_id") - public @Nullable String userId; - - @SerializedName("home_region") - public @Nullable String homeRegion; - - public @Nullable String name; - - @SerializedName("given_name") - public @Nullable String givenName; - } - - public static class Tokens { - @SerializedName("website_cookies") - public @Nullable Object websiteCookies; - - @SerializedName("mac_dms") - public @Nullable MacDms macDms; - - public @Nullable Bearer bearer; - } - - public static class MacDms { - @SerializedName("device_private_key") - public @Nullable String devicePrivateKey; - - @SerializedName("adp_token") - public @Nullable String adpToken; - } - - public static class Bearer { - @SerializedName("access_token") - public @Nullable String accessToken; - - @SerializedName("refresh_token") - public @Nullable String refreshToken; - - @SerializedName("expires_in") - public @Nullable String expiresIn; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonStartRoutineRequest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonStartRoutineRequest.java deleted file mode 100644 index e760c170b0..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonStartRoutineRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonStartRoutineRequest} encapsulate the GSON for starting a routine - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonStartRoutineRequest { - public @Nullable String behaviorId = "PREVIEW"; - public @Nullable String sequenceJson; - public @Nullable String status = "ENABLED"; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java deleted file mode 100644 index 8cec3eb2cf..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonUsersMeResponse} encapsulate the GSON data of the users me response - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonUsersMeResponse { - public @Nullable String countryOfResidence; - public @Nullable String effectiveMarketPlaceId; - public @Nullable String email; - public @Nullable Boolean eulaAcceptance; - public @Nullable List features; - public @Nullable String fullName; - public @Nullable Boolean hasActiveDopplers; - public @Nullable String id; - public @Nullable String marketPlaceDomainName; - public @Nullable String marketPlaceId; - public @Nullable String marketPlaceLocale; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWakeWords.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWakeWords.java deleted file mode 100644 index de6af9a8b2..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWakeWords.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link JsonWakeWords} encapsulate the GSON data of the wake word request - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonWakeWords { - public @Nullable List wakeWords; - - public static class WakeWord { - public @Nullable Boolean active; - public @Nullable String deviceSerialNumber; - public @Nullable String deviceType; - public @Nullable Object midFieldState; - public @Nullable String wakeWord; - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java deleted file mode 100644 index 7ecc3ad182..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.jsons; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link JsonWebSiteCookie} encapsulate the GSON data of register cookie array - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class JsonWebSiteCookie { - public JsonWebSiteCookie(String name, String value) { - this.name = name; - this.value = value; - } - - @SerializedName("Value") - public @Nullable String value; - @SerializedName("Name") - public @Nullable String name; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushConnection.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushConnection.java new file mode 100644 index 0000000000..ef7af4ef9e --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushConnection.java @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.push; + +import static org.eclipse.jetty.http.HttpHeader.*; +import static org.eclipse.jetty.http.HttpMethod.GET; +import static org.eclipse.jetty.http.HttpVersion.HTTP_2; +import static org.smarthomej.binding.amazonechocontrol.internal.push.PushConnection.State.*; + +import java.net.InetSocketAddress; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.ErrorCode; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.client.HTTP2Client; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.http2.frames.ResetFrame; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushCommandTO; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushMessageTO; + +import com.google.gson.Gson; + +/** + * The {@link PushConnection} handles the HTTP/2 push connection + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PushConnection implements PushSessionHandler.Listener, PushStreamAdapter.Listener { + private static final long CONNECTION_TIMEOUT = 10; + private static final int PING_TIMEOUT = 10; + private static final String PUSH_STREAM_ID = "push-stream"; + + private final Logger logger = LoggerFactory.getLogger(PushConnection.class); + private final HTTP2Client http2Client; + private final Gson gson; + private final Listener listener; + private final ScheduledExecutorService scheduler; + + private State state = CLOSED; + private @Nullable Session session; + private @Nullable ScheduledFuture waitForPing; + + public PushConnection(HTTP2Client http2Client, Gson gson, Listener listener, ScheduledExecutorService scheduler) { + this.http2Client = http2Client; + this.gson = gson; + this.listener = listener; + this.scheduler = scheduler; + } + + public State getState() { + return state; + } + + private void setState(State newState) { + this.state = newState; + listener.onPushConnectionStateChange(state); + } + + public void open(String amazonSite, String accessToken) { + cancelWaitForPing(); + Session session = this.session; + if (state != CLOSED || session != null) { + logger.warn( + "Tried to open a new session, but the the current state is {} - session hash {}. Please enable TRACE logging and report a bug.", + state, session != null ? session.hashCode() : ""); + return; + } + setState(State.CONNECTING); + + String host = switch (amazonSite) { + case "amazon.com" -> "bob-dispatch-prod-na.amazon.com"; + case "amazon.com.br" -> "bob-dispatch-prod-na.amazon.com"; + case "amazon.co.jp" -> "bob-dispatch-prod-fe.amazon.com"; + default -> "bob-dispatch-prod-eu.amazon.com"; + }; + + InetSocketAddress address = new InetSocketAddress(host, 443); + PushSessionHandler sessionHandler = new PushSessionHandler(this); + Promise.Completable sessionPromise = new Promise.Completable<>(); + sessionPromise.orTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS).handle((newSession, throwable) -> { + if (throwable != null) { + logger.warn("Failed to create session: {}", throwable.getMessage()); + setState(FAILED); + close(); + } else { + logger.trace("Created session with hash {}.", newSession.hashCode()); + this.session = newSession; + openPushStream(newSession, host, accessToken); + } + return null; + }); + http2Client.connect(http2Client.getBean(SslContextFactory.class), address, sessionHandler, sessionPromise); + } + + public void sendPing() { + Session session = this.session; + // we need to be connected, have a non-closes session and no running ping-pong + if (state == CONNECTED && session != null && !session.isClosed() && waitForPing == null) { + logger.trace("Sending ping in session {}", session.hashCode()); + waitForPing = scheduler.schedule(this::close, PING_TIMEOUT, TimeUnit.SECONDS); + session.ping(new PingFrame(false), Callback.NOOP); + } else if (state != CLOSED && session != null && session.isClosed()) { + close(); + } + } + + public void close() { + cancelWaitForPing(); + setState(State.DISCONNECTING); + Session session = this.session; + if (session != null && !session.isClosed()) { + session.getStreams().stream().filter(s -> s.getAttribute(PUSH_STREAM_ID) != null && !s.isReset()).forEach( + s -> s.reset(new ResetFrame(s.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP)); + session.close(ErrorCode.NO_ERROR.code, null, Callback.NOOP); + } + this.session = null; + setState(CLOSED); + } + + private void openPushStream(Session session, String host, String accessToken) { + HttpFields headerFields = new HttpFields(); + headerFields.put(USER_AGENT, "okhttp/4.3.2-SNAPSHOT"); + headerFields.put(AUTHORIZATION, "Bearer " + accessToken); + HttpURI uri = new HttpURI("https://" + host + "/v20160207/directives"); + HeadersFrame headers = new HeadersFrame(new MetaData.Request(GET.asString(), uri, HTTP_2, headerFields), null, + false); + + PushStreamAdapter eventListener = new PushStreamAdapter(gson, session, this); + Promise.Completable streamPromise = new Promise.Completable<>(); + streamPromise.orTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS).handle((stream, throwable) -> { + if (throwable != null) { + logger.warn("Failed to open stream in session {}: {}", session.hashCode(), throwable.getMessage()); + setState(FAILED); + } else { + logger.debug("Successfully initiated stream for session {}.", session.hashCode()); + stream.setIdleTimeout(0); + stream.setAttribute(PUSH_STREAM_ID, session); + setState(State.CONNECTED); + } + return null; + }); + session.newStream(headers, streamPromise, eventListener); + } + + @Override + public void onSessionClosed(int sessionHashCode) { + cancelWaitForPing(); + Session currentSession = session; + if (currentSession != null && currentSession.hashCode() == sessionHashCode) { + setState(CLOSED); + this.session = null; + } else { + logger.debug("Received a session closed for session {}, but the current session is {}", sessionHashCode, + currentSession != null ? currentSession.hashCode() : ""); + } + } + + @Override + public void onSessionFailed(int sessionHashCode) { + cancelWaitForPing(); + Session currentSession = session; + if (currentSession != null && currentSession.hashCode() == sessionHashCode) { + setState(FAILED); + } else { + logger.debug("Received a session failed for session {}, but the current session is {}", sessionHashCode, + currentSession != null ? currentSession.hashCode() : ""); + } + } + + @Override + public void onPushMessageReceived(PushMessageTO.RenderingUpdateTO renderingUpdate) { + PushCommandTO pushCommand = gson.fromJson(renderingUpdate.resourceMetadata, PushCommandTO.class); + if (pushCommand != null) { + listener.onPushCommandReceived(pushCommand); + } + } + + @Override + public void onSessionPingReceived() { + logger.trace("Cancelling pingWaitJob"); + cancelWaitForPing(); + } + + private void cancelWaitForPing() { + ScheduledFuture waitForPing = this.waitForPing; + if (waitForPing != null) { + waitForPing.cancel(true); + this.waitForPing = null; + } + } + + public interface Listener { + void onPushConnectionStateChange(State state); + + void onPushCommandReceived(PushCommandTO pushCommand); + } + + public enum State { + CLOSED, + CONNECTING, + CONNECTED, + DISCONNECTING, + FAILED + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushSessionHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushSessionHandler.java new file mode 100644 index 0000000000..273881df43 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushSessionHandler.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.push; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PushSessionHandler} handles the HTTP/2 push session + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PushSessionHandler extends Session.Listener.Adapter { + private final Logger logger = LoggerFactory.getLogger(PushSessionHandler.class); + private final Listener listener; + + public PushSessionHandler(Listener listener) { + this.listener = listener; + } + + @Override + public void onClose(@NonNullByDefault({}) Session session, @NonNullByDefault({}) GoAwayFrame frame) { + logger.debug("Session {} closed, reason {}", session.hashCode(), frame.getError()); + listener.onSessionClosed(session.hashCode()); + } + + @Override + public void onFailure(@NonNullByDefault({}) Session session, @NonNullByDefault({}) Throwable failure) { + logger.warn("Session {} failed: {}", session.hashCode(), failure.getMessage()); + listener.onSessionFailed(session.hashCode()); + } + + @Override + public void onPing(@NonNullByDefault({}) Session session, @NonNullByDefault({}) PingFrame frame) { + logger.trace("Session {} received pingFrame (reply={})", session.hashCode(), frame.isReply()); + if (!frame.isReply()) { + // answer only if this is not a reply + session.ping(new PingFrame(true), Callback.NOOP); + } else { + listener.onSessionPingReceived(); + } + } + + public interface Listener { + void onSessionClosed(int sessionHashCode); + + void onSessionFailed(int sessionHashCode); + + void onSessionPingReceived(); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushStreamAdapter.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushStreamAdapter.java new file mode 100644 index 0000000000..80a0dffbf1 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/push/PushStreamAdapter.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.push; + +import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http2.api.Session; +import org.eclipse.jetty.http2.api.Stream; +import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PingFrame; +import org.eclipse.jetty.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.dto.push.PushMessageTO; +import org.smarthomej.binding.amazonechocontrol.internal.util.HttpUtil; + +import com.google.gson.Gson; + +/** + * The {@link PushStreamAdapter} handles the HTTP/2 push stream + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class PushStreamAdapter extends Stream.Listener.Adapter { + private final Logger logger = LoggerFactory.getLogger(PushStreamAdapter.class); + private final Gson gson; + private final Session session; + private final Listener listener; + private String boundary = ""; + + public PushStreamAdapter(Gson gson, Session session, Listener listener) { + this.gson = gson; + this.session = session; + this.listener = listener; + } + + @Override + public void onHeaders(@NonNullByDefault({}) Stream stream, @NonNullByDefault({}) HeadersFrame frame) { + HttpFields headers = frame.getMetaData().getFields(); + if (logger.isTraceEnabled()) { + logger.trace("Received headers: {}", HttpUtil.logToString(headers)); + } + String contentType = headers.get(CONTENT_TYPE); + if (contentType == null || contentType.isBlank()) { + logger.warn("Headers of HTTP/2 stream don't contain content-type"); + return; + } + int boundaryStart = contentType.indexOf("boundary="); + int boundaryEnd = contentType.indexOf(";", boundaryStart); + boundary = contentType.substring(boundaryStart + 9, boundaryEnd); + } + + @Override + public void onData(@NonNullByDefault({}) Stream stream, @NonNullByDefault({}) DataFrame frame, + @NonNullByDefault({}) Callback callback) { + byte[] contentBuffer = new byte[frame.remaining()]; + frame.getData().get(contentBuffer); + String contentString = new String(contentBuffer); + logger.trace("Received raw data {}", contentString); + + // process + try { + if (boundary.isBlank()) { + logger.debug("Discarding message because boundary is not set"); + return; + } + BufferedReader contentReader = new BufferedReader(new StringReader(contentString)); + List content = contentReader.lines().filter(line -> !line.isBlank()).toList(); + + if (content.isEmpty()) { + return; + } + + if (!content.get(content.size() - 1).endsWith(boundary)) { + logger.debug("Discarding incomplete message, boundary not found"); + } + + if (content.size() == 1) { + // only boundary requires a PING response + logger.debug("Sending ping"); + session.ping(new PingFrame(false), Callback.NOOP); + } else if (content.get(0).equals("Content-Type: application/json")) { + // parse the message + PushMessageTO parsedMessage = Objects + .requireNonNullElse(gson.fromJson(content.get(1), PushMessageTO.class), new PushMessageTO()); + parsedMessage.directive.payload.renderingUpdates.forEach(listener::onPushMessageReceived); + } else { + logger.warn("Don't know how to handle frame starting with {}", content.get(0)); + } + } catch (RuntimeException e) { + logger.warn("Exception while processing message", e); + } + } + + public interface Listener { + void onPushMessageReceived(PushMessageTO.RenderingUpdateTO renderingUpdate); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AbstractInterfaceHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AbstractInterfaceHandler.java index ae39c26170..6f82be2d85 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AbstractInterfaceHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AbstractInterfaceHandler.java @@ -31,10 +31,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability.Properties; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability.Properties; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColor.java index 20e4dbc08e..1bfaabd08c 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColor.java @@ -12,12 +12,9 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.smarthome; -import java.util.List; -import java.util.stream.Collectors; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.library.types.HSBType; -import org.smarthomej.commons.util.ResourceUtil; +import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; /** * The {@link AlexaColor} defines the Alexa color names @@ -26,7 +23,6 @@ */ @NonNullByDefault public class AlexaColor { - public static final List ALEXA_COLORS = getColors(); public final String colorName; final HSBType value; @@ -48,7 +44,7 @@ public static String getClosestColorName(HSBType value) { double[] lab = getLabFromHSB(value); String colorName = ""; double smallestDistance = Double.MAX_VALUE; - for (AlexaColor color : ALEXA_COLORS) { + for (AlexaColor color : AmazonEchoControlBindingConstants.ALEXA_COLORS) { double distance = color.getEuclideanDistance(lab); if (distance < smallestDistance) { colorName = color.colorName; @@ -94,9 +90,4 @@ private static double labRoot(double value) { return Math.pow(value, 1.0 / 3.0); } } - - private static List getColors() { - return ResourceUtil.readProperties(AlexaColor.class, "color.properties").entrySet().stream() - .map(e -> new AlexaColor(e.getKey(), new HSBType(e.getValue()))).collect(Collectors.toList()); - } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/Constants.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/Constants.java index 73e38605b7..a58690b55f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/Constants.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/Constants.java @@ -13,13 +13,14 @@ */ package org.smarthomej.binding.amazonechocontrol.internal.smarthome; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_ID; + import java.util.Map; import java.util.Set; import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.type.ChannelTypeUID; -import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; /** @@ -49,64 +50,40 @@ public class Constants { public static final Set 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_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_AIR_QUALITY_INDOOR_AIR_QUALITY = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "indoorAirQuality"); - public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_HUMIDITY = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "humidity"); - public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_PM25 = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "pm25"); - public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_CARBON_MONOXIDE = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "carbonMonoxide"); - public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_VOC = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "voc"); - public static final ChannelTypeUID CHANNEL_TYPE_FAN_SPEED = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "fanSpeed"); - public static final ChannelTypeUID CHANNEL_TYPE_POWER_STATE = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "powerState"); - public static final ChannelTypeUID CHANNEL_TYPE_LOCK_STATE = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "lockState"); - public static final ChannelTypeUID CHANNEL_TYPE_UID_ACOUSTIC_EVENT_DETECTION = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "acousticEventDetectionState"); - public static final ChannelTypeUID CHANNEL_TYPE_BRIGHTNESS = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "brightness"); - public static final ChannelTypeUID CHANNEL_TYPE_COLOR_NAME = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "colorName"); - public static final ChannelTypeUID CHANNEL_TYPE_COLOR = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "color"); - public static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEMPERATURE_NAME = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "colorTemperatureName"); - public static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEMPERATURE_IN_KELVIN = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "colorTemperatureInKelvin"); - public static final ChannelTypeUID CHANNEL_TYPE_PERCENTAGE = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "percentage"); - public static final ChannelTypeUID CHANNEL_TYPE_POWER_LEVEL = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "powerLevel"); - public static final ChannelTypeUID CHANNEL_TYPE_ARM_STATE = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "armState"); - public static final ChannelTypeUID CHANNEL_TYPE_BURGLARY_ALARM = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "burglaryAlarm"); - public static final ChannelTypeUID CHANNEL_TYPE_CARBON_MONOXIDE_ALARM = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "carbonMonoxideAlarm"); - public static final ChannelTypeUID CHANNEL_TYPE_FIRE_ALARM = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "fireAlarm"); - public static final ChannelTypeUID CHANNEL_TYPE_WATER_ALARM = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "waterAlarm"); - public static final ChannelTypeUID CHANNEL_TYPE_MOTION_DETECTED = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "motionDetected"); - public static final ChannelTypeUID CHANNEL_TYPE_CONTACT_STATUS = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "contact"); - public static final ChannelTypeUID CHANNEL_TYPE_GEOLOCATION = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "geoLocation"); - public static final ChannelTypeUID CHANNEL_TYPE_CONNECTIVITY = new ChannelTypeUID( - AmazonEchoControlBindingConstants.BINDING_ID, "connectivity"); + public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID(BINDING_ID, "temperature"); + public static final ChannelTypeUID CHANNEL_TYPE_TARGETSETPOINT = new ChannelTypeUID(BINDING_ID, "targetSetpoint"); + public static final ChannelTypeUID CHANNEL_TYPE_LOWERSETPOINT = new ChannelTypeUID(BINDING_ID, "lowerSetpoint"); + public static final ChannelTypeUID CHANNEL_TYPE_UPPERSETPOINT = new ChannelTypeUID(BINDING_ID, "upperSetpoint"); + public static final ChannelTypeUID CHANNEL_TYPE_THERMOSTATMODE = new ChannelTypeUID(BINDING_ID, "thermostatMode"); + public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_INDOOR_AIR_QUALITY = new ChannelTypeUID(BINDING_ID, + "indoorAirQuality"); + public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_HUMIDITY = new ChannelTypeUID(BINDING_ID, "humidity"); + public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_PM25 = new ChannelTypeUID(BINDING_ID, "pm25"); + public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_CARBON_MONOXIDE = new ChannelTypeUID(BINDING_ID, + "carbonMonoxide"); + public static final ChannelTypeUID CHANNEL_TYPE_AIR_QUALITY_VOC = new ChannelTypeUID(BINDING_ID, "voc"); + public static final ChannelTypeUID CHANNEL_TYPE_FAN_SPEED = new ChannelTypeUID(BINDING_ID, "fanSpeed"); + public static final ChannelTypeUID CHANNEL_TYPE_POWER_STATE = new ChannelTypeUID(BINDING_ID, "powerState"); + public static final ChannelTypeUID CHANNEL_TYPE_LOCK_STATE = new ChannelTypeUID(BINDING_ID, "lockState"); + public static final ChannelTypeUID CHANNEL_TYPE_UID_ACOUSTIC_EVENT_DETECTION = new ChannelTypeUID(BINDING_ID, + "acousticEventDetectionState"); + public static final ChannelTypeUID CHANNEL_TYPE_BRIGHTNESS = new ChannelTypeUID(BINDING_ID, "brightness"); + public static final ChannelTypeUID CHANNEL_TYPE_COLOR_NAME = new ChannelTypeUID(BINDING_ID, "colorName"); + public static final ChannelTypeUID CHANNEL_TYPE_COLOR = new ChannelTypeUID(BINDING_ID, "color"); + public static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEMPERATURE_NAME = new ChannelTypeUID(BINDING_ID, + "colorTemperatureName"); + public static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEMPERATURE_IN_KELVIN = new ChannelTypeUID(BINDING_ID, + "colorTemperatureInKelvin"); + public static final ChannelTypeUID CHANNEL_TYPE_PERCENTAGE = new ChannelTypeUID(BINDING_ID, "percentage"); + public static final ChannelTypeUID CHANNEL_TYPE_POWER_LEVEL = new ChannelTypeUID(BINDING_ID, "powerLevel"); + public static final ChannelTypeUID CHANNEL_TYPE_ARM_STATE = new ChannelTypeUID(BINDING_ID, "armState"); + public static final ChannelTypeUID CHANNEL_TYPE_BURGLARY_ALARM = new ChannelTypeUID(BINDING_ID, "burglaryAlarm"); + public static final ChannelTypeUID CHANNEL_TYPE_CARBON_MONOXIDE_ALARM = new ChannelTypeUID(BINDING_ID, + "carbonMonoxideAlarm"); + public static final ChannelTypeUID CHANNEL_TYPE_FIRE_ALARM = new ChannelTypeUID(BINDING_ID, "fireAlarm"); + public static final ChannelTypeUID CHANNEL_TYPE_WATER_ALARM = new ChannelTypeUID(BINDING_ID, "waterAlarm"); + public static final ChannelTypeUID CHANNEL_TYPE_MOTION_DETECTED = new ChannelTypeUID(BINDING_ID, "motionDetected"); + public static final ChannelTypeUID CHANNEL_TYPE_CONTACT_STATUS = new ChannelTypeUID(BINDING_ID, "contact"); + public static final ChannelTypeUID CHANNEL_TYPE_GEOLOCATION = new ChannelTypeUID(BINDING_ID, "geoLocation"); + public static final ChannelTypeUID CHANNEL_TYPE_CONNECTIVITY = new ChannelTypeUID(BINDING_ID, "connectivity"); } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java index 3e49559450..e32ca054e2 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java @@ -23,9 +23,9 @@ import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.types.Command; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java index 8621f14cbd..7d79e3f88f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java @@ -26,9 +26,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java index ba116c3b03..312abbc0d5 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -35,10 +34,11 @@ import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; @@ -153,8 +153,9 @@ public boolean handleCommand(Connection connection, JsonSmartHomeDevice shd, Str public @Nullable List getCommandDescription(Channel channel) { String channelId = channel.getUID().getId(); if (COLOR_PROPERTIES.channelId.equals(channelId)) { - return AlexaColor.ALEXA_COLORS.stream().map(color -> new CommandOption(color.colorName, color.colorName)) - .sorted(Comparator.comparing(CommandOption::getCommand)).collect(Collectors.toList()); + return AmazonEchoControlBindingConstants.ALEXA_COLORS.stream() + .map(color -> new CommandOption(color.colorName, color.colorName)) + .sorted(Comparator.comparing(CommandOption::getCommand)).toList(); } return null; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java index 1a1b1343bf..b488a96a27 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java @@ -25,9 +25,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerContactSensor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerContactSensor.java index 105046df56..c863ade465 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerContactSensor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerContactSensor.java @@ -22,9 +22,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerEndpointHealth.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerEndpointHealth.java index aaf22ecbaf..c798e9a8cb 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerEndpointHealth.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerEndpointHealth.java @@ -29,9 +29,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerHumiditySensor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerHumiditySensor.java index 332c0f1a4a..ee2e94b563 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerHumiditySensor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerHumiditySensor.java @@ -28,9 +28,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLocation.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLocation.java index e3b438ba2c..c03bc40cfb 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLocation.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLocation.java @@ -23,9 +23,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLockController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLockController.java index 2587edaeca..10bdd802ea 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLockController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerLockController.java @@ -23,9 +23,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerMotionSensor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerMotionSensor.java index 153a7f69ae..36493321d2 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerMotionSensor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerMotionSensor.java @@ -22,9 +22,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java index 7769459902..8e4b50dfb5 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java @@ -26,9 +26,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java index 1c0edd2a92..ed16ed148f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -23,9 +24,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; @@ -75,10 +76,10 @@ public boolean handleCommand(Connection connection, JsonSmartHomeDevice shd, Str if (channelId.equals(POWER_STATE.channelId)) { if (containsCapabilityProperty(capabilities, POWER_STATE.propertyName)) { if (command.equals(OnOffType.ON)) { - connection.smartHomeCommand(entityId, "turnOn"); + connection.smartHomeCommand(entityId, "turnOn", Map.of()); return true; } else if (command.equals(OnOffType.OFF)) { - connection.smartHomeCommand(entityId, "turnOff"); + connection.smartHomeCommand(entityId, "turnOff", Map.of()); return true; } } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java index b4e77e1a5e..c3cb20df45 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java @@ -26,9 +26,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerRangeController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerRangeController.java index aefb5abcaa..9dc11d0975 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerRangeController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerRangeController.java @@ -36,9 +36,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java index 89c27b8f68..2734fee861 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java @@ -25,9 +25,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java index 5aeb176c20..b0ded2e1e2 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java @@ -29,9 +29,9 @@ import org.openhab.core.types.Command; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerThermostatController.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerThermostatController.java index 18d826f23e..66921cd60f 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerThermostatController.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/HandlerThermostatController.java @@ -34,9 +34,9 @@ import org.openhab.core.types.Type; import org.openhab.core.types.UnDefType; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/InterfaceHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/InterfaceHandler.java index 0a2a86db5d..19e97ccca3 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/InterfaceHandler.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/InterfaceHandler.java @@ -23,8 +23,8 @@ import org.openhab.core.types.CommandOption; import org.openhab.core.types.StateDescription; import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeCapability; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; import com.google.gson.JsonObject; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/JsonNetworkDetails.java similarity index 91% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/JsonNetworkDetails.java index 8e46565b15..eeb6bae8c8 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/JsonNetworkDetails.java @@ -11,7 +11,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.jsons; +package org.smarthomej.binding.amazonechocontrol.internal.smarthome; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java index 179eee36a8..2bc1759667 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java @@ -23,8 +23,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice.DriverIdentity; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice; +import org.smarthomej.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice.DriverIdentity; /** * Handles the update interval calculation diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketException.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Announcement.java similarity index 50% rename from bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketException.java rename to bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Announcement.java index b96fbea435..420dc87ac4 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketException.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Announcement.java @@ -10,25 +10,21 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.smarthomej.binding.amazonechocontrol.internal.websocket; +package org.smarthomej.binding.amazonechocontrol.internal.types; -import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; /** - * The {@link WebsocketException} is a + * The {@link Announcement} encapsulates the information for an announcement command via the + * {@link org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants#CHANNEL_ANNOUNCEMENT} + * channel. * * @author Jan N. Klug - Initial contribution */ -@NonNullByDefault -public class WebsocketException extends Exception { - private static final long serialVersionUID = 1L; - - public WebsocketException(String message) { - super(message); - } - - public WebsocketException(String message, @Nullable Throwable cause) { - super(message, cause); - } +public class Announcement { + public @Nullable Boolean sound; + public @Nullable String title; + public @Nullable String body; + public @Nullable String speak; + public @Nullable Integer volume; } diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Notification.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Notification.java new file mode 100644 index 0000000000..50f6b3c74d --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/types/Notification.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.types; + +import java.time.ZonedDateTime; + +/** + * The {@link Notification} encapsulates a notification + * + * @author Jan N. Klug - Initial contribution + */ +public record Notification(String deviceSerial, String type, ZonedDateTime nextAlarmTime) { +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpRequestBuilder.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpRequestBuilder.java new file mode 100644 index 0000000000..2da8e0a128 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpRequestBuilder.java @@ -0,0 +1,443 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import static org.eclipse.jetty.http.HttpHeader.*; +import static org.eclipse.jetty.http.HttpMethod.*; +import static org.eclipse.jetty.http.HttpStatus.*; +import static org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400; +import static org.eclipse.jetty.http.MimeTypes.Type.APPLICATION_JSON_UTF_8; +import static org.eclipse.jetty.http.MimeTypes.Type.FORM_ENCODED; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.API_VERSION; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DI_OS_VERSION; +import static org.smarthomej.binding.amazonechocontrol.internal.util.HttpRequestBuilder.FailMode.*; + +import java.net.CookieManager; +import java.net.HttpCookie; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.ws.rs.core.MediaType; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.client.util.BufferingResponseListener; +import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.amazonechocontrol.internal.ConnectionException; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; + +/** + * The {@link HttpRequestBuilder} creates customized requests for Alexa API requests + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class HttpRequestBuilder { + private static final String DEFAULT_USER_AGENT = "AmazonWebView/Amazon Alexa/" + API_VERSION + "/iOS/" + + DI_OS_VERSION + "/iPhone"; + + private final Logger logger = LoggerFactory.getLogger(HttpRequestBuilder.class); + + private final CookieManager cookieManager; + private final HttpClient httpClient; + private final Gson gson; + private final Lock lock = new ReentrantLock(); + private final Semaphore semaphore = new Semaphore(2, true); + + public HttpRequestBuilder(HttpClient httpClient, CookieManager cookieManager, Gson gson) { + this.httpClient = httpClient; + this.cookieManager = cookieManager; + this.gson = gson; + } + + public Builder get(String uriString) { + return new Builder(GET, uriString); + } + + public Builder post(String uriString) { + return new Builder(POST, uriString); + } + + public Builder put(String uriString) { + return new Builder(PUT, uriString); + } + + public Builder delete(String uriString) { + return new Builder(DELETE, uriString); + } + + public Builder builder(HttpMethod httpMethod, String uriString) { + return new Builder(httpMethod, uriString); + } + + private void createRequest(URI uri, RequestParams params, HttpResponseListener responseListener) { + Request request = httpClient.newRequest(uri).method(params.method()); + request.header(ACCEPT_LANGUAGE, "en-US"); + request.header("DNT", "1"); + request.header("Upgrade-Insecure-Requests", "1"); + if (!params.customHeaders().containsKey(USER_AGENT.toString())) { + request.agent(DEFAULT_USER_AGENT); + } + params.customHeaders().entrySet().stream().filter(h -> !h.getValue().isBlank()) + .forEach(h -> request.header(h.getKey(), h.getValue())); + + // handle re-directs in response listener manually + request.followRedirects(false); + + // add cookies + + if (!params.customHeaders().containsKey(COOKIE.toString())) { + for (HttpCookie cookie : cookieManager.getCookieStore().get(uri)) { + request.cookie(cookie); + if (cookie.getName().equals("csrf")) { + request.header("csrf", cookie.getValue()); + } + } + } + + if (params.requestContent() != null) { + byte[] contentBytes = params.requestContent().getBytes(StandardCharsets.UTF_8); + request.header(CONTENT_TYPE, params.json() ? APPLICATION_JSON_UTF_8.asString() : FORM_ENCODED.asString()); + request.header(CONTENT_LENGTH, Integer.toString(contentBytes.length)); + if (POST.equals(params.method())) { + request.header(EXPECT, "100-continue"); + } + request.content(new BytesContentProvider(contentBytes)); + } + + if (logger.isTraceEnabled()) { + logger.trace("> {} to {}, headers = {}, cookies = {}, content = {}", params.method(), uri, + HttpUtil.logToString(request.getHeaders()), request.getCookies(), params.requestContent()); + } + + request.send(responseListener); + } + + /** + * The {@link Builder} is used to build HTTP requests to remote servers, including managed cookies + */ + public class Builder { + private final HttpMethod httpMethod; + private final URI uri; + private final Map headers = new HashMap<>(); + private boolean retry = true; + private boolean redirect = true; + private boolean isJson = false; + private @Nullable String body; + + private Builder(HttpMethod httpMethod, String uriString) { + this.httpMethod = httpMethod; + this.uri = URI.create(uriString); + } + + /** + * Adds a single header to this request + * + * @param field the field name + * @param value the value + * @return the request builder + */ + public Builder withHeader(String field, String value) { + this.headers.put(field, value); + return this; + } + + /** + * Add multiple headers to this request + * + * @param headers a {@link Map} containing the headers + * @return the request builder + */ + public Builder withHeaders(Map headers) { + this.headers.putAll(headers); + return this; + } + + /** + * Set the retry flag + * + * @param retry {@code true} allows up to 3 retries (default), {@code false} fails immediately + * @return the request builder + */ + public Builder retry(boolean retry) { + this.retry = retry; + return this; + } + + /** + * Set the redirect flag + * + * @param redirect {@code true} allows up to 30 redirects (default), {@code false} fails on + * redirection + * @return the request builder + */ + public Builder redirect(boolean redirect) { + this.redirect = redirect; + return this; + } + + public Builder withContent(@Nullable Object content) { + if (content == null || content instanceof String) { + this.body = (String) content; + this.isJson = false; + } else if (content instanceof JsonObject) { + this.body = content.toString(); + this.isJson = true; + } else { + this.body = gson.toJson(content); + this.isJson = true; + } + return this; + } + + /** + * Override the autodetected type + *

+ * This needs to be called AFTER the content has been set + * + * @param isJson if the request content should be considered as JSON + * @return the request builder + */ + public Builder withJson(boolean isJson) { + this.isJson = isJson; + return this; + } + + public CompletableFuture send() { + RequestParams params = new RequestParams(httpMethod, body, isJson, headers); + CompletableFuture httpResponse = new CompletableFuture<>(); + HttpResponseListener responseListener = new HttpResponseListener(httpResponse, params, redirect, + retry ? RETRY : EXCEPTION); + createRequest(uri, params, responseListener); + return httpResponse; + } + + public CompletableFuture send(Class returnType) { + return send(TypeToken.get(returnType)); + } + + @SuppressWarnings("unchecked") + public CompletableFuture send(TypeToken returnType) { + return send().thenApply(response -> { + if (returnType.getRawType().equals(String.class)) { + return (T) response.content; + } + if (returnType.getRawType().equals(HttpRequestBuilder.HttpResponse.class)) { + return (T) response; + } + String contentType = response.headers.get(CONTENT_TYPE); + if (!contentType.startsWith(MediaType.APPLICATION_JSON)) { + logger.debug("JSON conversion to {} was requested but the response has a Content-Type {}", + returnType.getType().getTypeName(), contentType); + } + try { + T returnValue = gson.fromJson(response.content(), returnType); + // gson.fromJson is non-null if json is non-null and not empty + if (returnValue == null) { + throw new JsonParseException("Empty result"); + } + return returnValue; + } catch (JsonParseException e) { + logger.warn("Parsing json failed: {}", isJson, e); + throw e; + } + }); + } + + public HttpResponse syncSend() throws ConnectionException { + return syncSend(HttpResponse.class); + } + + public T syncSend(Class returnType) throws ConnectionException { + return syncSend(TypeToken.get(returnType)); + } + + public T syncSend(TypeToken returnType) throws ConnectionException { + try { + logger.debug("> {}: {} (available: {})", httpMethod, uri, semaphore.availablePermits()); + semaphore.acquire(); + return send(returnType).get(); + } catch (ExecutionException e) { + if (e.getCause() instanceof ConnectionException connectionException) { + throw connectionException; + } else { + throw new ConnectionException("Request failed", e); + } + } catch (RuntimeException | InterruptedException e) { + logger.debug("Request to uri '{}' failed:", uri, e); + throw new ConnectionException("Request failed", e); + } finally { + semaphore.release(); + } + } + } + + private class HttpResponseListener extends BufferingResponseListener { + private static final int MAX_REDIRECTS = 30; + private static final int MAX_RETRIES = 3; + + private final Logger logger = LoggerFactory.getLogger(HttpResponseListener.class); + private final CompletableFuture httpResponse; + private final RequestParams params; + private final boolean autoRedirect; + private final FailMode failMode; + private int redirectCounter = MAX_REDIRECTS; + private int retryCounter = MAX_RETRIES; + + public HttpResponseListener(CompletableFuture httpResponse, RequestParams requestParams, + boolean autoRedirect, FailMode failMode) { + this.httpResponse = httpResponse; + this.params = requestParams; + this.autoRedirect = autoRedirect; + this.failMode = failMode; + } + + private HttpResponseListener(HttpResponseListener other, int retryCounter, int redirectCounter) { + this.httpResponse = other.httpResponse; + this.params = other.params; + this.autoRedirect = other.autoRedirect; + this.failMode = other.failMode; + this.retryCounter = retryCounter; + this.redirectCounter = redirectCounter; + } + + @Override + public void onComplete(Result result) { + Response response = result.getResponse(); + URI requestUri = response.getRequest().getURI(); + int responseStatus = response.getStatus(); + HttpFields headers = Objects.requireNonNull(response.getHeaders()); + String content = Objects.requireNonNullElse(getContentAsString(), ""); + + if (logger.isTraceEnabled()) { + logger.trace("< {} to {}: {}, headers = {}, content = {}", params.method(), requestUri, responseStatus, + HttpUtil.logToString(response.getHeaders()), content); + } else { + logger.debug("< {} to {}: {}", params.method, requestUri, responseStatus); + } + + headers.getFields(SET_COOKIE).forEach(cookieHeader -> HttpCookie.parse(cookieHeader.getValue()) + .forEach(cookie -> cookieManager.getCookieStore().add(requestUri, cookie))); + + String location = headers.get(LOCATION); + if (location != null && !location.isBlank()) { + location = requestUri.resolve(location).toString(); + if (location.toLowerCase().startsWith("http://")) { + // always use https + location = "https://" + location.substring(7); + logger.debug("Redirect corrected to {}", location); + } + } + + if (HttpStatus.isSuccess(responseStatus)) { + httpResponse.complete(new HttpResponse(responseStatus, headers, content)); + } else if (isRedirection(responseStatus) && location != null) { + logger.debug("Redirected to {}", location); + if (!autoRedirect) { + httpResponse.complete(new HttpResponse(responseStatus, headers, content)); + } + if (redirectCounter == 0) { + httpResponse.completeExceptionally(new ConnectionException("Too many redirects")); + } + createRequest(URI.create(location), params, + new HttpResponseListener(this, retryCounter, redirectCounter - 1)); + } else if (responseStatus == BAD_REQUEST_400 + && "QUEUE_EXPIRED".equals(response.getHeaders().get("x-amzn-error"))) { + // handle queue expired + httpResponse.completeExceptionally(new ConnectionException("Queue expired")); + } else { + if (failMode == EXCEPTION || retryCounter == 0) { + if (responseStatus == 0) { + httpResponse.completeExceptionally(new ConnectionException("Request aborted.")); + } + httpResponse.completeExceptionally(new ConnectionException( + requestUri + " failed with code " + responseStatus + ": " + response.getReason())); + } else if (failMode == NORMAL) { + httpResponse.complete(new HttpResponse(responseStatus, headers, content)); + } else { + logger.debug("Retrying call to {}", requestUri); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + httpResponse.completeExceptionally(new ConnectionException("Interrupted", e)); + } + createRequest(requestUri, params, + new HttpResponseListener(this, retryCounter - 1, redirectCounter)); + } + } + } + } + + private record RequestParams(HttpMethod method, @Nullable String requestContent, boolean json, + Map customHeaders) { + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RequestParams that)) { + return false; + } + return json == that.json && method == that.method && Objects.equals(requestContent, that.requestContent) + && Objects.equals(customHeaders, that.customHeaders); + } + + public int hashCode() { + return Objects.hash(method, requestContent, json, customHeaders); + } + } + + public record HttpResponse(int statusCode, HttpFields headers, String content) { + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + HttpResponse response = (HttpResponse) o; + return statusCode == response.statusCode && Objects.equals(headers, response.headers) + && Objects.equals(content, response.content); + } + + @Override + public int hashCode() { + return Objects.hash(statusCode, headers, content); + } + } + + public enum FailMode { + NORMAL, + EXCEPTION, + RETRY + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpUtil.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpUtil.java new file mode 100644 index 0000000000..53f2dbabb3 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/HttpUtil.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.http.HttpFields; + +/** + * The {@link HttpUtil} implements utility methods for HTTP requests + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class HttpUtil { + + private HttpUtil() { + // prevent instantiation + } + + public static String logToString(HttpFields httpFields) { + return "[" + httpFields.stream().map(field -> { + String headerName = field.getName(); + String value = field.getValue(); + return headerName + "=" + value; + }).collect(Collectors.joining(",")) + "]"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/NonNullListTypeAdapterFactory.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/NonNullListTypeAdapterFactory.java new file mode 100644 index 0000000000..251737f81b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/NonNullListTypeAdapterFactory.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * The {@link NonNullListTypeAdapterFactory} is a {@link TypeAdapterFactory} for allowing annotation based + * null-serialization + *

+ * Fields that shall be serialized even if they are null need a {@link SerializeNull} annotation + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class NonNullListTypeAdapterFactory implements TypeAdapterFactory { + @Override + @SuppressWarnings("unchecked") + public @Nullable TypeAdapter create(@NonNullByDefault({}) Gson gson, + @NonNullByDefault({}) TypeToken type) { + + Class rawType = (Class) type.getRawType(); + if (rawType != List.class) { + return null; + } + TypeAdapter delegateAdapter = gson.getDelegateAdapter(NonNullListTypeAdapterFactory.this, type); + + return new DeserializeNonNullTypeAdapter<>(delegateAdapter); + } + + private static class DeserializeNonNullTypeAdapter extends TypeAdapter { + private final TypeAdapter delegateTypeAdapter; + + public DeserializeNonNullTypeAdapter(TypeAdapter delegateTypeAdapter) { + this.delegateTypeAdapter = delegateTypeAdapter; + } + + @Override + public void write(JsonWriter writer, @Nullable T value) throws IOException { + delegateTypeAdapter.write(writer, value); + } + + @Override + @SuppressWarnings("unchecked") + public @Nullable T read(JsonReader reader) throws IOException { + final JsonToken peek = reader.peek(); + if (peek == JsonToken.NULL) { + reader.nextNull(); + return (T) List.of(); + } + return delegateTypeAdapter.read(reader); + } + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNull.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNull.java new file mode 100644 index 0000000000..14761ffc73 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNull.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The {@link SerializeNull} annotation is used to indicate that a field should be serialized even if it is null + * + * @author Jan N. Klug - Initial contribution + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SerializeNull { +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNullTypeAdapterFactory.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNullTypeAdapterFactory.java new file mode 100644 index 0000000000..7961269284 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/SerializeNullTypeAdapterFactory.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * The {@link SerializeNullTypeAdapterFactory} is a {@link TypeAdapterFactory} for allowing annotation based + * null-serialization + *

+ * Fields that shall be serialized even if they are null need a {@link SerializeNull} annotation + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class SerializeNullTypeAdapterFactory implements TypeAdapterFactory { + @Override + public @Nullable TypeAdapter create(@NonNullByDefault({}) Gson gson, + @NonNullByDefault({}) TypeToken type) { + List fields = Arrays.asList(type.getRawType().getFields()); + if (fields.stream().noneMatch(field -> field.isAnnotationPresent(SerializeNull.class))) { + // this type has no fields annotated with @SerializeNull, so we don't return a type adapter for this one + return null; + } + + List nonNullFields = fields.stream().filter(field -> !field.isAnnotationPresent(SerializeNull.class)) + .map(this::getRealName).toList(); + + TypeAdapter delegateAdapter = gson.getDelegateAdapter(SerializeNullTypeAdapterFactory.this, type); + TypeAdapter elementAdapter = gson.getAdapter(JsonElement.class); + + return new SerializeNullTypeAdapter<>(delegateAdapter, elementAdapter, nonNullFields); + } + + private String getRealName(Field field) { + SerializedName serializedName = field.getAnnotation(SerializedName.class); + return serializedName != null ? serializedName.value() : field.getName(); + } + + private static class SerializeNullTypeAdapter extends TypeAdapter { + private final TypeAdapter delegateTypeAdapter; + private final TypeAdapter elementTypeAdapter; + private final List nonNullFields; + + public SerializeNullTypeAdapter(TypeAdapter delegateTypeAdapter, TypeAdapter elementTypeAdapter, + List nonNullFields) { + this.delegateTypeAdapter = delegateTypeAdapter; + this.elementTypeAdapter = elementTypeAdapter; + this.nonNullFields = nonNullFields; + } + + @Override + public void write(JsonWriter writer, @Nullable T value) throws IOException { + JsonObject jsonObject = delegateTypeAdapter.toJsonTree(value).getAsJsonObject(); + + // remove all null-fields that are not annotated with @SerializeNull + nonNullFields.forEach(fieldName -> removeNullFields(jsonObject, fieldName)); + + writer.setSerializeNulls(true); + elementTypeAdapter.write(writer, jsonObject); + writer.setSerializeNulls(false); + } + + @Override + public T read(JsonReader reader) throws IOException { + return delegateTypeAdapter.read(reader); + } + + private void removeNullFields(JsonObject jsonObject, String fieldName) { + if (jsonObject.has(fieldName) && jsonObject.get(fieldName).isJsonNull()) { + jsonObject.remove(fieldName); + } + } + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/Util.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/Util.java new file mode 100644 index 0000000000..1e0e73c64b --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/util/Util.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.util; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link Util} contains helper methods + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class Util { + + private Util() { + // prevent instantiation + } + + public static Optional findIn(Collection collection, Function keyExtractor, + @Nullable U searchKey) { + return collection.stream().filter(e -> Objects.equals(searchKey, keyExtractor.apply(e))).findAny(); + } + + public static List filterList(List list, Function keyExtractor, U searchKey) { + return list.stream().filter(e -> Objects.equals(searchKey, keyExtractor.apply(e))).toList(); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/AlexaWebSocket.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/AlexaWebSocket.java deleted file mode 100644 index d53620265f..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/AlexaWebSocket.java +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.websocket; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.concurrent.ThreadLocalRandom; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; -import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPushCommand; - -import com.google.gson.Gson; - -/** - * The {@link AlexaWebSocket} is a websocket implementation for Alexa connections - * - * @author Jan N. Klug - Initial contribution - */ -@WebSocket(maxTextMessageSize = 64 * 1024, maxBinaryMessageSize = 64 * 1024) -@NonNullByDefault -public class AlexaWebSocket { - private final Logger logger = LoggerFactory.getLogger(AlexaWebSocket.class); - - private final Gson gson; - private final WebSocketConnection webSocketConnection; - private final WebSocketCommandHandler webSocketCommandHandler; - - boolean initialize = false; - int messageId; - private @Nullable Session session; - - public AlexaWebSocket(WebSocketConnection webSocketConnection, WebSocketCommandHandler webSocketCommandHandler, - Gson gson) { - this.webSocketConnection = webSocketConnection; - this.webSocketCommandHandler = webSocketCommandHandler; - this.gson = gson; - this.messageId = ThreadLocalRandom.current().nextInt(0, Short.MAX_VALUE); - } - - @OnWebSocketConnect - @SuppressWarnings("unused") - public void onWebSocketConnect(@Nullable Session session) { - if (session != null) { - this.initialize = true; - this.session = session; - webSocketConnection.onConnect(); - } else { - logger.debug("Web Socket connect without session"); - } - } - - @OnWebSocketMessage - @SuppressWarnings("unused") - public void onWebSocketBinary(byte @Nullable [] data, int offset, int len) { - if (data == null) { - return; - } - if (logger.isDebugEnabled()) { - logger.debug("received {} bytes: {}", data.length, printableString(data)); - } - - byte[] buffer = data; - if (offset > 0 || len != buffer.length) { - buffer = Arrays.copyOfRange(data, offset, offset + len); - } - WebsocketMessage message = new WebsocketMessage(buffer, gson); - try { - if (initialize) { - initialize = false; - sendMessage( - "0xfe88bc52 0x0000009c {\"protocolName\":\"A:F\",\"parameters\":{\"AlphaProtocolHandler.receiveWindowSize\":\"16\",\"AlphaProtocolHandler.maxFragmentSize\":\"16000\"}}TUNE" - .getBytes(StandardCharsets.UTF_8)); - Thread.sleep(40); - sendMessage(encodeGWRegister()); - Thread.sleep(40); - sendPing(); - } else { - if (message.service.equals("FABE") && message.content.messageType.equals("PON") - && message.content.payloadData.length > 0) { - logger.debug("Pong received"); - webSocketConnection.clearPongTimeoutTimer(); - } else { - JsonPushCommand pushCommand = message.content.pushCommand; - logger.trace("Message received: {}", message.content.payload); - if (pushCommand != null) { - webSocketCommandHandler.webSocketCommandReceived(pushCommand); - } - } - } - } catch (Exception e) { - logger.debug("Handling of push notification failed", e); - } - } - - @OnWebSocketMessage - @SuppressWarnings("unused") - public void onWebSocketText(@Nullable String message) { - logger.trace("Received text message: '{}'", message); - } - - @OnWebSocketClose - @SuppressWarnings("unused") - public void onWebSocketClose(@Nullable Session session, int code, @Nullable String reason) { - if (session != null) { - session.close(); - } - logger.info("Web Socket close {}. Reason: {}", code, reason); - webSocketConnection.close(); - } - - @OnWebSocketError - @SuppressWarnings("unused") - public void onWebSocketError(@Nullable Session session, @Nullable Throwable error) { - if (session != null) { - session.close(); - } - logger.info("Web Socket error: {}", error == null ? "" : error.getMessage()); - webSocketConnection.close(); - } - - private String printableString(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - if (b < 32) { - sb.append("\\x").append(String.format("%02x", b)); - } else { - sb.append((char) b); - } - } - return sb.toString(); - } - - private void sendMessage(byte[] buffer) { - try { - if (logger.isDebugEnabled()) { - logger.debug("Send message with length {}: {}", buffer.length, printableString(buffer)); - } - Session session = this.session; - if (session != null) { - session.getRemote().sendBytes(ByteBuffer.wrap(buffer)); - } else { - logger.warn("Tried to send message '{}' but session is null. Looks like a bug!", buffer); - } - } catch (IOException e) { - logger.debug("Send message failed", e); - webSocketConnection.close(); - } - } - - public void sendPing() { - logger.debug("Send Ping"); - webSocketConnection.initPongTimeoutTimer(); - sendMessage(encodePing()); - } - - /** - * compute checksum: sum of unsigned int 32 + number of overflows - * - * @param data byte array of input data - * @return the calculated checksum - */ - private int computeChecksum(byte[] data) { - ByteBuffer buffer = ByteBuffer.allocate(data.length + (4 - data.length % 4)); - buffer.put(data).position(0); - - long sum = 0; - while (buffer.hasRemaining()) { - sum += buffer.getInt() & 0xffffffffL; - } - - long overflow = sum >>> 32; // get overflow count - sum = sum & 0xffffffffL; // coerce to unsigned int32 - - while (overflow != 0) { - sum += overflow; - overflow = sum >>> 32; - sum = sum & 0xffffffffL; - } - - return (int) sum; - } - - private ByteBuffer getMessageHeader(int packetLength, int messageType) { - this.messageId++; - - ByteBuffer buffer = ByteBuffer.allocate(packetLength); - buffer.put("MSG".getBytes(StandardCharsets.UTF_8)); - buffer.putInt(messageType); // Message-type and Channel - buffer.putInt(this.messageId); - buffer.put((byte) 102); // 'f' - buffer.putInt(0x00000001); - buffer.putInt(0x00000000); // place for checksum - buffer.putInt(packetLength); // packet length - - return buffer; - } - - private byte[] encodeGWRegister() { - ByteBuffer buffer = getMessageHeader(0x000000e4, 0x00000362); - - byte[] msg = "GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {\"command\":\"REGISTER_CONNECTION\"}FABE" - .getBytes(StandardCharsets.UTF_8); - buffer.put(msg); - - int checksum = computeChecksum(buffer.array()); - buffer.putInt(16, checksum); - - return buffer.array(); - } - - private byte[] encodePing() { - ByteBuffer buffer = getMessageHeader(0x0000003d, 0x00000065); - - buffer.put("PIN".getBytes(StandardCharsets.UTF_8)); - buffer.putInt(0x00000000); - buffer.putLong(System.currentTimeMillis()); - - // 7-byte payload "Regular" as UTF-16BE - buffer.putInt(0x00000007); - buffer.put("Regular".getBytes(StandardCharsets.UTF_16BE)); - - buffer.put("FABE".getBytes(StandardCharsets.UTF_8)); - - int checksum = computeChecksum(buffer.array()); - buffer.putInt(16, checksum); - - return buffer.array(); - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketCommandHandler.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketCommandHandler.java deleted file mode 100644 index 002aab0d12..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketCommandHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.websocket; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPushCommand; - -/** - * The {@link WebSocketCommandHandler} is used for the web socket handler implementation - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public interface WebSocketCommandHandler { - - public void webSocketCommandReceived(JsonPushCommand pushCommand); -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketConnection.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketConnection.java deleted file mode 100644 index f4ea44f68e..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebSocketConnection.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.websocket; - -import java.io.IOException; -import java.net.HttpCookie; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.Objects; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.Future; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; -import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.MacDms; - -import com.google.gson.Gson; - -/** - * The {@link WebSocketConnection} encapsulate the Web Socket connection to the amazon server. - * The code is based on - * https://github.com/Apollon77/alexa-remote/blob/master/alexa-wsmqtt.js - * - * @author Michael Geramb - Initial contribution - * @author Ingo Fischer - (https://github.com/Apollon77/alexa-remote/blob/master/alexa-wsmqtt.js) - */ -@NonNullByDefault -public class WebSocketConnection { - private final Logger logger = LoggerFactory.getLogger(WebSocketConnection.class); - - private final WebSocketClient webSocketClient; - private final AlexaWebSocket alexaWebSocket; - private final String adpToken; - private final PrivateKey privateKey; - - private @Nullable Timer pingTimer; - private @Nullable Timer pongTimeoutTimer; - private @Nullable Future sessionFuture; - - private boolean closed = false; - - public WebSocketConnection(Connection connection, WebSocketCommandHandler webSocketCommandHandler, Gson gson, - HttpClient httpClient) throws WebsocketException { - String amazonSite = connection.getAmazonSite(); - List sessionCookies = connection.getSessionCookies(connection.getAlexaServer()); - MacDms macDms = connection.getMacDms(); - if (macDms == null) { - throw new WebsocketException("Web socket failed: Could not get macDMS."); - } - - this.adpToken = Objects.requireNonNullElse(macDms.adpToken, ""); - - try { - byte[] encoded = Base64.getMimeDecoder().decode(macDms.devicePrivateKey); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); - this.privateKey = keyFactory.generatePrivate(keySpec); - } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { - throw new WebsocketException("Could not determine private key"); - } - - alexaWebSocket = new AlexaWebSocket(this, webSocketCommandHandler, gson); - webSocketClient = new WebSocketClient(httpClient); - - try { - String host; - if ("amazon.com".equalsIgnoreCase(amazonSite)) { - host = "dp-gw-na-js." + amazonSite; - } else { - host = "dp-gw-na." + amazonSite; - } - - List cookiesForWs = new ArrayList<>(); - for (HttpCookie cookie : sessionCookies) { - // Clone the cookie without the security attribute, because the web socket implementation ignore secure - // cookies - String value = cookie.getValue().replaceAll("^\"|\"$", ""); - HttpCookie cookieForWs = new HttpCookie(cookie.getName(), value); - cookiesForWs.add(cookieForWs); - } - URI uri; - - uri = new URI("wss://" + host + "/tcomm/"); - - try { - webSocketClient.start(); - } catch (Exception e) { - logger.warn("Web socket start failed", e); - throw new WebsocketException("Web socket start failed."); - } - - ClientUpgradeRequest request = new ClientUpgradeRequest(); - request.setHeader("Host", host); - request.setHeader("Origin", "https://alexa." + amazonSite); - request.setHeader("x-dp-comm-tuning", "A:F;A:H"); - request.setHeader("x-dp-reason", "ClientInitiated;1"); - request.setHeader("x-dp-tcomm-purpose", "Regular"); - // 'x-dp-deviceVersion': 'motorola/osprey_reteu_2gb/osprey_u2:6.0.1/MPI24.107-55/33:user/release-keys', - // 'x-dp-networkType': 'WIFI', - // 'x-dp-tcomm-versionCode': '894920010', - // 'x-dp-oui': 'dca632', - request.setHeader("x-dp-obfuscatedBssid", "-2019514039"); - request.setHeader("x-dp-tcomm-versionName", "2.2.443692.0"); - request.setHeader("x-adp-signature", sign("GET", "/tcomm/", "")); - request.setHeader("x-adp-token", adpToken); - request.setHeader("x-adp-alg", "SHA256WithRSA:1.0"); - request.setCookies(cookiesForWs); - - initPongTimeoutTimer(); - - sessionFuture = webSocketClient.connect(alexaWebSocket, uri, request); - } catch (URISyntaxException | IOException e) { - throw new WebsocketException("Failed to initialize websocket.", e); - } - } - - public boolean isClosed() { - return closed; - } - - public void close() { - closed = true; - final Future sessionFuture = this.sessionFuture; - if (sessionFuture != null) { - sessionFuture.cancel(true); - } - - Timer pingTimer = this.pingTimer; - if (pingTimer != null) { - pingTimer.cancel(); - } - clearPongTimeoutTimer(); - logger.trace("Connect future = {}", sessionFuture); - try { - webSocketClient.stop(); - } catch (InterruptedException e) { - // Just ignore - } catch (Exception e) { - logger.warn("Stopping websocket failed", e); - } - - webSocketClient.destroy(); - } - - void onConnect() { - Timer pingTimer = new Timer(); - this.pingTimer = pingTimer; - pingTimer.schedule(new TimerTask() { - - @Override - public void run() { - alexaWebSocket.sendPing(); - } - }, 180000, 180000); - } - - void clearPongTimeoutTimer() { - Timer pongTimeoutTimer = this.pongTimeoutTimer; - this.pongTimeoutTimer = null; - if (pongTimeoutTimer != null) { - logger.trace("Cancelling pong timeout"); - pongTimeoutTimer.cancel(); - } - } - - void initPongTimeoutTimer() { - clearPongTimeoutTimer(); - Timer pongTimeoutTimer = new Timer(); - this.pongTimeoutTimer = pongTimeoutTimer; - logger.trace("Scheduling pong timeout"); - pongTimeoutTimer.schedule(new TimerTask() { - - @Override - public void run() { - logger.trace("Pong timeout reached. Closing connection."); - close(); - } - }, 60000); - } - - private @Nullable String sign(String method, String path, String body) { - try { - Signature privateSignature = Signature.getInstance("SHA256withRSA"); - String now = ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS) - .format(DateTimeFormatter.ISO_INSTANT); - - privateSignature.initSign(privateKey); - privateSignature.update((method + "\n").getBytes(StandardCharsets.UTF_8)); - privateSignature.update((path + "\n").getBytes(StandardCharsets.UTF_8)); - privateSignature.update((now + "\n").getBytes(StandardCharsets.UTF_8)); - privateSignature.update((body + "\n").getBytes(StandardCharsets.UTF_8)); - privateSignature.update((adpToken).getBytes(StandardCharsets.UTF_8)); - - byte[] signature = privateSignature.sign(); - return Base64.getEncoder().encodeToString(signature) + ":" + now; - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - return null; - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessage.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessage.java deleted file mode 100644 index b64b1ce16b..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessage.java +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.websocket; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPushCommand; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link WebsocketMessage} encapsulates an incoming websocket message - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -@SuppressWarnings("unused") -public class WebsocketMessage { - private final Logger logger = LoggerFactory.getLogger(WebsocketMessage.class); - - public final String service; - public final WebsocketMessageContent content = new WebsocketMessageContent(); - public final String contentTune; - public final String messageType; - public final long channel; - public final long checksum; - public final long messageId; - public final String moreFlag; - public final long seq; - - public WebsocketMessage(byte[] bytes, Gson gson) { - Buffer buffer = new Buffer(bytes); - - service = new String(bytes, bytes.length - 4, 4, StandardCharsets.UTF_8); - - if ("TUNE".equals(service)) { - checksum = buffer.getNextInt4(); - int contentLength = (int) buffer.getNextInt4(); - contentTune = buffer.getNextString(contentLength - buffer.getIndex() - 4); - channel = 0; - messageId = 0; - seq = 0; - messageType = ""; - moreFlag = ""; - } else if ("FABE".equals(service)) { - contentTune = ""; - messageType = buffer.getNextString(3, false); - channel = buffer.getNextInt4b(); - messageId = buffer.getNextInt4b(); - moreFlag = buffer.getNextString(1, false); - seq = buffer.getNextInt4b(); - checksum = buffer.getNextInt4b(); - long contentLength = buffer.getNextInt4b(); // currently unused - content.messageType = buffer.getNextString(3); - - if (channel == 0x361) { // GW_HANDSHAKE_CHANNEL - if (content.messageType.equals("ACK")) { - int length = (int) buffer.getNextInt4(); - content.protocolVersion = buffer.getNextString(length); - length = (int) buffer.getNextInt4(); - content.connectionUUID = buffer.getNextString(length); - content.established = buffer.getNextInt4(); - content.timestampINI = buffer.getNextInt8(); - content.timestampACK = buffer.getNextInt8(); - } - } else if (channel == 0x362) { // GW_CHANNEL - if (content.messageType.equals("GWM")) { - content.subMessageType = buffer.getNextString(3); - content.channel = buffer.getNextInt4(); - - if (content.channel == 0xb479) { // DEE_WEBSITE_MESSAGING - int length = (int) buffer.getNextInt4(); - content.destinationIdentityUrn = buffer.getNextString(length); - length = (int) buffer.getNextInt4(); - String idData = buffer.getNextString(length); - - String[] idDataElements = idData.split(" ", 2); - content.deviceIdentityUrn = idDataElements[0]; - String payload = null; - if (idDataElements.length == 2) { - payload = idDataElements[1]; - } - if (payload == null) { - int index = buffer.getIndex(); - payload = new String(bytes, index, bytes.length - 4 - index); - } - if (!payload.isEmpty()) { - try { - content.pushCommand = gson.fromJson(payload, JsonPushCommand.class); - } catch (JsonSyntaxException e) { - logger.info("Parsing json failed, illegal JSON: {}", payload, e); - } - } - content.payload = payload; - } - } - } else if (channel == 0x65) { // CHANNEL_FOR_HEARTBEAT - content.payloadData = Arrays.copyOfRange(bytes, buffer.getIndex() - 1, bytes.length - 4); - } - } else { - throw new IllegalArgumentException("Not a valid websocket message"); - } - } - - static class Buffer { - private final byte[] buffer; - private int index = 0; - private final ByteBuffer byteBuffer; - - public Buffer(byte[] buffer) { - this.buffer = buffer; - this.byteBuffer = ByteBuffer.wrap(buffer); - } - - public String getNextString(int length) { - return getNextString(length, true); - } - - public String getNextString(int length, boolean delimiter) { - if (index + length <= buffer.length) { - String string = new String(buffer, index, length, StandardCharsets.UTF_8); - index = index + length + (delimiter ? 1 : 0); // including one delimiter - return string; - } - throw new IllegalStateException("No more bytes left"); - } - - /** - * get 4-byte integer - * - * @return the next 4-byte integer as long - */ - public long getNextInt4() { - return getNextInt(8); // each byte has two hex characters - } - - public long getNextInt4b() { - index += 4; - return byteBuffer.getInt(index - 4); - } - - /** - * get 8-byte integer - * - * @return the next 8-byte integer as long - */ - public long getNextInt8() { - return getNextInt(16); // each byte has two hex characters - } - - public int getIndex() { - return index; - } - - private long getNextInt(int length) { - String str = getNextString(length + 2); // including prefix "0x" - if (str.startsWith("0x")) { - return Long.parseLong(str.substring(2), 16); - } - throw new NumberFormatException(); - } - } -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessageContent.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessageContent.java deleted file mode 100644 index d189d3d176..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/java/org/smarthomej/binding/amazonechocontrol/internal/websocket/WebsocketMessageContent.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2010-2021 Contributors to the openHAB project - * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.websocket; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonPushCommand; - -/** - * The {@link WebsocketMessageContent} encapsulates the content of an incoming websocket message - * - * @author Jan N. Klug - Initial contribution - */ -@NonNullByDefault -public class WebsocketMessageContent { - public String messageType = ""; - public String protocolVersion = ""; - public String connectionUUID = ""; - public long established; - public long timestampINI; - public long timestampACK; - public String subMessageType = ""; - public long channel; - public String destinationIdentityUrn = ""; - public String deviceIdentityUrn = ""; - public @Nullable String payload; - public byte[] payloadData = new byte[0]; - public @Nullable JsonPushCommand pushCommand; -} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml index 589e37396d..da40f13c91 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/thing/thing-types.xml @@ -7,7 +7,11 @@ Amazon Account where the amazon echo devices are registered. + + + 1 + @@ -39,6 +43,12 @@ 120 + + + The number of seconds between a voice command was detected and the received command is requested from + the server. + 10 + @@ -65,11 +75,6 @@ - - - - - @@ -91,7 +96,7 @@ - 2 + 3 serialNumber @@ -125,11 +130,6 @@ - - - - - @@ -151,7 +151,7 @@ - 2 + 3 serialNumber @@ -185,11 +185,6 @@ - - - - - @@ -211,7 +206,7 @@ - 2 + 3 serialNumber @@ -238,12 +233,10 @@ - - - - - + + 1 + serialNumber @@ -292,10 +285,11 @@ - + Switch - Save the current flash briefing configuration (Write only) + Save the current flash briefing configuration (write-only) + veto Switch @@ -306,6 +300,7 @@ String Plays the briefing on the device (serial number or name, write only) + veto String @@ -313,20 +308,18 @@ Connected bluetooth device - - String - - String Speak the reminder and send a notification to the Alexa app + veto String - The command which must be spoken to active the routing without the preceding "Alexa," (Write Only) + The command which must be spoken to active the routing without the preceding "Alexa," (write-only) + veto String @@ -334,27 +327,6 @@ Plays an alarm sound veto - - String - - Id of the amazon music track - - - Switch - - Amazon Music turned on - - - String - - Amazon Music play list id (Write only, no current state) - veto - - - String - - Id of the playlist which was started with openHAB - String @@ -387,11 +359,6 @@ - - Switch - - Radio turned on - Switch @@ -442,25 +409,35 @@ String - Voice command as text. E.g. 'Yesterday from the Beatles' (Write only) + Voice command as text. E.g. 'Yesterday from the Beatles' (write-only) + veto String - Sends a message to the Echo devices (Write only). + Sends a message to the Echo devices (write-only). + veto + + + Switch + + A command send to this channel refreshes the customer history activity (write-only) + veto String - Display the announcement message on the display (Write only). See in the tutorial section of the binding + 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. + veto String - Speak the text (Write only). It is possible to use plain text or SSML: <speak>I want to tell you a + 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> + veto Dimmer @@ -470,7 +447,8 @@ String - Run a command (Write only). The command can run like a spoken command. + Run a command (write-only). The command can run like a spoken command. + veto String @@ -501,7 +479,7 @@ String - Start information (Write only) + Start information (write-only) veto diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 0000000000..7f1c46bb7a --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,82 @@ + + + + + + + amazonechocontrol:refreshActivity + + + + + + + amazonechocontrol:lastSpokenText + + + + + amazonechocontrol:doNotDisturb + + + + + + + + + + + + + + + amazonechocontrol:lastSpokenText + + + + + amazonechocontrol:doNotDisturb + + + + + + + + + + + + + + + amazonechocontrol:lastSpokenText + + + + + amazonechocontrol:doNotDisturb + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/account-detail.vm b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/account-detail.vm new file mode 100644 index 0000000000..b991b81995 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/account-detail.vm @@ -0,0 +1,95 @@ +#* @vtlvariable name="connection" type="org.smarthomej.binding.amazonechocontrol.internal.connection.Connection" *# +#* @vtlvariable name="accountPath" type="java.lang.String" *# +#* @vtlvariable name="servletPath" type="java.lang.String" *# +#* @vtlvariable name="account" type="org.smarthomej.binding.amazonechocontrol.internal.handler.AccountHandler" *# +#* @vtlvariable name="devices" type="java.util.List" *# +#* @vtlvariable name="DEVICE_TYPES" type="java.util.Map" *# + + + + AmazonEchoControl - Account details + + + +

+ Logout + Logout and Re-register + Binding Overview +

+

+ Account "$account.thing.label" + ($account.thing.UID) + $account.thing.status +

+ + + + + + + + + + + + + + + + + + + + + + +
App Name$connection.loginData.deviceName
Customer Name$connection.customerName
Customer Id$connection.loginData.accountCustomerId
Connected to$connection.alexaServer
Logged in since$connection.loginData.loginTime
+ +

Connected Devices

+ + #if($devices.isEmpty()) +

No devices in account.

+ #else + + + + + + + + + #foreach($device in $devices) + #set($thing = $account.getThingBySerialNumber($device.serialNumber)) + #if($device.online) + #set($deviceStatus = "ONLINE") + #else + #set($deviceStatus = "OFFLINE") + #end + + + + + + + + #end +
NameThingSerialnumberDevice TypeFamily
+ $device.accountName + $deviceStatus + + #if($thing) + $thing.label + $thing.status + #else + NONE + #end + $device.serialNumber + #if($device.deviceType) + $DEVICE_TYPES.getOrDefault($device.deviceType, $device.deviceType) + #else + unknown + #end + $device.deviceFamily
+ #end + + diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/binding.vm b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/binding.vm new file mode 100644 index 0000000000..0fda0a99e3 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/binding.vm @@ -0,0 +1,25 @@ +#* @vtlvariable name="servletPath" type="java.lang.String" *# +#* @vtlvariable name="accounts" type="java.util.List" *# + + + + AmazonEchoControl - Binding overview + + + +

+ Binding Overview +

+

Configured Accounts

+ + #foreach($account in $accounts) + + + #end +
+ $account.thing.label ($account.thing.UID) + $account.thing.status + $account.lastKnownDevices.size() child device(s)
+ + + diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/default.css b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/default.css new file mode 100644 index 0000000000..af4ed25a20 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/default.css @@ -0,0 +1,93 @@ +:root { + --aec-text-color: #202070; + --aec-background-color: #f0f7ff; + --aec-border: 1px solid black; +} + +body { + font-family: Arial, Helvetica, sans-serif; + font-size: 3mm; + color: var(--aec-text-color); + background: var(--aec-background-color); + line-height: 1.5em; + margin: .75em; + padding: .75em; + +} + +code { + font-family: monospace; + font-size: 0.9em; + font-weight: normal; +} + +h1 { + padding: .75em; + background: var(--aec-text-color); + color: var(--aec-background-color); + border-radius: 5px; +} + +h2 { + padding:.75em; + background: lightblue; + color: black; + border-radius: 5px; +} + +h3 { + padding: .75em; + text-align: right; +} + +a, a:visited { + color: var(--aec-text-color); + margin-left: 0.5em; + margin-right: 0.5em; + white-space: nowrap; +} +td>a, td>a:visited { + margin-right: 0; + margin-left: 0; +} + +th, td { + text-align: left; + border: 1px solid black; + padding: .5em; +} + +.table-bordered { + border: var(--aec-border); + border-collapse: collapse; + padding: 0.5em; + margin: .75em; + font-size: 1.2em; + border-radius: 5px; + border-style: hidden; + box-shadow: 0 0 0 1px black; +} + +.status { + float: right; + border-radius: 8px; + display: inline-block; + margin-left: 2em; + padding: 3px; + font-size: .9em; + width: 4.5em; + text-align: center; + color:white; +} + +.OFFLINE { + background-color: red; +} + +.ONLINE { + background-color: green; +} + +.UNKNOWN { + background-color: grey; +} \ No newline at end of file diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/device-detail.vm b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/device-detail.vm new file mode 100644 index 0000000000..ed7b67c58e --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/WEB-INF/device-detail.vm @@ -0,0 +1,58 @@ +#* @vtlvariable name="accountPath" type="java.lang.String" *# +#* @vtlvariable name="servletPath" type="java.lang.String" *# +#* @vtlvariable name="thing" type="org.openhab.core.thing.Thing" *# +#* @vtlvariable name="channels" type="java.util.Map>" *# +#* @vtlvariable name="capabilities" type="java.util.List" *# + + + AmazonEchoControl - $thing.label + + + +

+ Account Overview + Binding Overview +

+

+ Device "$thing.label" + ($thing.UID) +

+ #foreach($channelInfo in $channels.entrySet()) + #set($channel = $thing.getChannel($channelInfo.key)) + #if($channel != "") #* only show if channel is available on device *# +

+ Channel "$channel.label" + ($channelInfo.key) +

+ #if($channelInfo.value.isEmpty()) +

No channel options found.

+ #else + + + + + + #foreach($option in $channelInfo.value) + + + + + #end +
ValueName
$option.value$option.displayName
+ #end + #end + #end +

Device Capabilities

+ #if ($capabilities.isEmpty()) +

No capabilities detected.

+ #else + + #foreach($capability in $capabilities) + + + + #end +
$capability
+ #end + + diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/device_type.properties b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/device_type.properties new file mode 100644 index 0000000000..99b70e8164 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/device_type.properties @@ -0,0 +1,101 @@ +A10A33FOX2NUBK = Echo Spot +A10L5JEZTKKCZ8 = Vobot-Clock +A11QM4H9HGV71H = Echo Show 5 3rd Gen +A12GXV8XMS007S = FireTV +A15ERDAKK5HQQG = Sonos +A17LGWINFBUTZZ = Anker Roav Viva Alexa +A18O6U1UQFJ0XK = Echo Plus 2.Gen +A1C66CX2XD756O = Fire HD 8 +A1DL2DVDQVK3Q = Apps +A1EIANJ7PNB0Q7 = Echo Show 15 +A1ETW4IXK2PYBP = Echo Auto +A1GIZO9LR81BL5 = Yamaha ATS-1090 +A1H0CMF1XM0ZP4 = Echo Dot/Bose +A1J16TEDOYCZTN = Fire tab +A1JJ0KFC4ZPNJ3 = Echo Input +A1LOQ8ZHF4G510 = Samsung Soundbar Q990B +A1NL4BVLQ4L3N3 = Echo Show +A1NQ0LXWBGVQS9 = Samsung The Frame +A1P31Q3MOWSHOD = Anker Zalo Halo Speaker +A1Q7QCGNMXAKYW = Fire tab 7 (Partial command support) +A1QKZ9D0IJY332 = Samsung QLED (Partial command support) +A1RABVCI4QCIKC = Echo Dot 3.Gen +A1RTAM01W29CUP = Windows App +A1X7HJX9QL16M5 = Bespoken.io +A1XWJRHALS1REP = Echo Show 5 2.Gen +A1Z88NGR2BK6A2 = Echo Show 8 +A1ZB65LA390I4K = Fire HD 10 +A21Z3CGI8UIP0F = Apps +A222D4HGE48EOR = Alexa App Apple Watch +A25OJWHZA1MWNB = Samsung TV Neo +A265XOI9586NML = FireTV Strick v3 +A2825NDLA7WDZV = Apps +A2DS1Q2TPDJ48U = Echo Dot 5.Gen Clock +A2E0SNTXJVT7WK = Fire TV V1 +A2EZ3TS0L1S2KV = Sonos Beam Gen 2 +A2GFL5ZMWNE0PX = Fire TV +A2H4LV5GIZ1JFT = Echo 4 Clock +A2IS7199CJBT71 = LG TV +A2IVLV5VM2W81 = Apps +A2J0R2SD7G9LPA = Tablet +A2JKHJ0PX4J3L3 = FireTV Cube +A2JMVL7QPIXTN5 = Amazfit +A2L8KG0CT86ADW = RaspPi +A2LWARUGJLBYEW = Fire TV Stick V2 +A2M35JJZWCQOMZ = Echo Plus +A2M4YX06LWP8WI = Fire Tab +A2OSP3UA4VC85F = Sonos +A2T0P32DY3F7VB = echosim.io +A2TF17PFR55MTB = Apps +A2U21SRK4QGSE1 = Echo Dot 4.Gen +A2XPGY5LRKB9BE = Fitbit Versa 2 +A2Y04QPFCANLPQ = Bose QC35-II +A2Z8O30CD35N8F = Sonos Arc +A303PJF6ISQ7IC = Echo Auto +A30YDR2MK8HMRV = Echo Dot 3.Gen Clock +A31DTMEEVDDOIV = Fire TV Stick Lite 2020 +A32DOYMUN6DTXA = Echo Dot 3.Gen +A378ND93PD0NC4 = VR Radio +A37SHHQ3NUL7B5 = Bose Homespeaker +A38BPK7OW001EX = Raspberry Alexa +A38EHHIB10L47V = Echo Dot +A39Y3UG1XLEJLZ = Fitbit Sense +A3C9PE6TNYLTCH = Multiroom +A3EVMLQTU6WL1W = Echo Show 8 2.Gen +A3FX4UWTP28V1P = Echo 3 +A3GZUE7F9MEB4U = Fire TV Cube +A3H674413M2EKB = echosim.io +A3HF4YRA2L7XGC = Fire TV Cube +A3L2K717GERE73 = Alexa App Apple Watch +A3NPD82ABCPIDP = Sonos Beam +A3OCOCNTAPDC9O = Mi Smart Band 6 NFC +A3R8XIAIU4HJAX = Echo Show +A3R9S4ZZECZ6YL = Fire Tab HD 10 +A3RBAYBE7VM004 = Echo Studio +A3RMGO6LYLH7YN = Echo Plus 4.Gen +A3S5BH2HU6VAYF = Echo Dot 2.Gen +A3SSG6GR8UU7SN = Echo Sub +A3TCJ8RTT3NVI7 = Listens for Alexa +A3V3VA38K169FO = Fire Tab +A3VRME03NAXFUB = Echo Flex +A4ZP7ZC4PI6TO = Echo Show 5 +A4ZXE0RM7LQ7A = Echo Dot 5.Gen +A7WXQPH584YP = Echo 2.Gen +A8D2OKFFQKQ56 = Bose Smart Soundbar 900 +A8DM4FYR6D3HT = LG WebOS TV +AB72C64C86AW2 = Echo +ADVBD696BHNV5 = Fire TV Stick V1 +AHCEDGRIFN5RP = Xiaomi Smart Fire TV +AHJYKVA63YCAQ = Sonos Roam +AILBSA2LNTOYL = reverb App +AINRG27IL8AS0 = Megablast Speaker +AIPK7MM90V7TB = Echo Show 10 +AKOAGQTKAS9YB = Echo Connect +AKPGW064GI9HE = Fire Stick 4K +AO50AHDYKXRFG = Bose Headphones +AP1F6KUH00XPV = Stereo/Subwoofer Pair +ATNLRCEBX3W4P = Fire HD 10 +AVD3HM0HOJAAL = Sonos One 2.Gen +AVE5HX13UR5NO = Logitech Zero Touch +AVU7CPPF2ZRAS = Fire HD 8 +AWZZ5CVHX2CD = Echo Show 2.Gen \ No newline at end of file diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/registration_capabilities.json b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/registration_capabilities.json new file mode 100644 index 0000000000..4d0de9f2a2 --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/registration_capabilities.json @@ -0,0 +1,276 @@ +{ + "legacyFlags": { + "SUPPORTS_COMMS": true, + "SUPPORTS_ARBITRATION": true, + "SCREEN_WIDTH": 1170, + "SUPPORTS_SCRUBBING": true, + "SPEECH_SYNTH_SUPPORTS_TTS_URLS": false, + "SUPPORTS_HOME_AUTOMATION": true, + "SUPPORTS_DROPIN_OUTBOUND": true, + "FRIENDLY_NAME_TEMPLATE": "VOX", + "SUPPORTS_SIP_OUTBOUND_CALLING": true, + "VOICE_PROFILE_SWITCHING_DISABLED": true, + "SUPPORTS_LYRICS_IN_CARD": false, + "SUPPORTS_DATAMART_NAMESPACE": "Vox", + "SUPPORTS_VIDEO_CALLING": true, + "SUPPORTS_PFM_CHANGED": true, + "SUPPORTS_TARGET_PLATFORM": "TABLET", + "SUPPORTS_SECURE_LOCKSCREEN": false, + "AUDIO_PLAYER_SUPPORTS_TTS_URLS": false, + "SUPPORTS_KEYS_IN_HEADER": false, + "SUPPORTS_MIXING_BEHAVIOR_FOR_AUDIO_PLAYER": false, + "AXON_SUPPORT": true, + "SUPPORTS_TTS_SPEECHMARKS": true + }, + "envelopeVersion": "20160207", + "capabilities": [ + { + "version": "0.1", + "interface": "CardRenderer", + "type": "AlexaInterface" + }, + { + "interface": "Navigation", + "type": "AlexaInterface", + "version": "1.1" + }, + { + "type": "AlexaInterface", + "version": "2.0", + "interface": "Alexa.Comms.PhoneCallController" + }, + { + "type": "AlexaInterface", + "version": "1.1", + "interface": "ExternalMediaPlayer" + }, + { + "type": "AlexaInterface", + "interface": "Alerts", + "configurations": { + "maximumAlerts": { + "timers": 2, + "overall": 99, + "alarms": 2 + } + }, + "version": "1.3" + }, + { + "version": "1.0", + "interface": "Alexa.Display.Window", + "type": "AlexaInterface", + "configurations": { + "templates": [ + { + "type": "STANDARD", + "id": "app_window_template", + "configuration": { + "sizes": [ + { + "id": "fullscreen", + "type": "DISCRETE", + "value": { + "value": { + "height": 1440, + "width": 3200 + }, + "unit": "PIXEL" + } + } + ], + "interactionModes": [ + "mobile_mode", + "auto_mode" + ] + } + } + ] + } + }, + { + "type": "AlexaInterface", + "interface": "AccessoryKit", + "version": "0.1" + }, + { + "type": "AlexaInterface", + "interface": "Alexa.AudioSignal.ActiveNoiseControl", + "version": "1.0", + "configurations": { + "ambientSoundProcessingModes": [ + { + "name": "ACTIVE_NOISE_CONTROL" + }, + { + "name": "PASSTHROUGH" + } + ] + } + }, + { + "interface": "PlaybackController", + "type": "AlexaInterface", + "version": "1.0" + }, + { + "version": "1.0", + "interface": "Speaker", + "type": "AlexaInterface" + }, + { + "version": "1.0", + "interface": "SpeechSynthesizer", + "type": "AlexaInterface" + }, + { + "version": "1.0", + "interface": "AudioActivityTracker", + "type": "AlexaInterface" + }, + { + "type": "AlexaInterface", + "interface": "Alexa.Camera.LiveViewController", + "version": "1.0" + }, + { + "type": "AlexaInterface", + "version": "1.0", + "interface": "Alexa.Input.Text" + }, + { + "type": "AlexaInterface", + "interface": "Alexa.PlaybackStateReporter", + "version": "1.0" + }, + { + "version": "1.1", + "interface": "Geolocation", + "type": "AlexaInterface" + }, + { + "interface": "Alexa.Health.Fitness", + "version": "1.0", + "type": "AlexaInterface" + }, + { + "interface": "Settings", + "type": "AlexaInterface", + "version": "1.0" + }, + { + "configurations": { + "interactionModes": [ + { + "dialog": "SUPPORTED", + "interactionDistance": { + "value": 18, + "unit": "INCHES" + }, + "video": "SUPPORTED", + "keyboard": "SUPPORTED", + "id": "mobile_mode", + "uiMode": "MOBILE", + "touch": "SUPPORTED" + }, + { + "video": "UNSUPPORTED", + "dialog": "SUPPORTED", + "interactionDistance": { + "value": 36, + "unit": "INCHES" + }, + "uiMode": "AUTO", + "touch": "SUPPORTED", + "id": "auto_mode", + "keyboard": "UNSUPPORTED" + } + ] + }, + "type": "AlexaInterface", + "interface": "Alexa.InteractionMode", + "version": "1.0" + }, + { + "type": "AlexaInterface", + "configurations": { + "catalogs": [ + { + "type": "IOS_APP_STORE", + "identifierTypes": [ + "URI_HTTP_SCHEME", + "URI_CUSTOM_SCHEME" + ] + } + ] + }, + "version": "0.2", + "interface": "Alexa.Launcher" + }, + { + "interface": "System", + "version": "1.0", + "type": "AlexaInterface" + }, + { + "interface": "Alexa.IOComponents", + "type": "AlexaInterface", + "version": "1.4" + }, + { + "type": "AlexaInterface", + "interface": "Alexa.FavoritesController", + "version": "1.0" + }, + { + "version": "1.0", + "type": "AlexaInterface", + "interface": "Alexa.Mobile.Push" + }, + { + "type": "AlexaInterface", + "interface": "InteractionModel", + "version": "1.1" + }, + { + "interface": "Alexa.PlaylistController", + "type": "AlexaInterface", + "version": "1.0" + }, + { + "interface": "SpeechRecognizer", + "type": "AlexaInterface", + "version": "2.1" + }, + { + "interface": "AudioPlayer", + "type": "AlexaInterface", + "version": "1.3" + }, + { + "type": "AlexaInterface", + "version": "3.1", + "interface": "Alexa.RTCSessionController" + }, + { + "interface": "VisualActivityTracker", + "version": "1.1", + "type": "AlexaInterface" + }, + { + "interface": "Alexa.PlaybackController", + "version": "1.0", + "type": "AlexaInterface" + }, + { + "type": "AlexaInterface", + "interface": "Alexa.SeekController", + "version": "1.0" + }, + { + "interface": "Alexa.Comms.MessagingController", + "type": "AlexaInterface", + "version": "1.0" + } + ] +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echo.update b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echo.update deleted file mode 100644 index eca8e06805..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echo.update +++ /dev/null @@ -1,2 +0,0 @@ -1;ADD_CHANNEL;lastSpokenText,String,amazonechocontrol:lastSpokenText -2;ADD_CHANNEL;doNotDisturb,Switch,amazonechocontrol:doNotDisturb diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echoshow.update b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echoshow.update deleted file mode 100644 index eca8e06805..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echoshow.update +++ /dev/null @@ -1,2 +0,0 @@ -1;ADD_CHANNEL;lastSpokenText,String,amazonechocontrol:lastSpokenText -2;ADD_CHANNEL;doNotDisturb,Switch,amazonechocontrol:doNotDisturb diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echospot.update b/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echospot.update deleted file mode 100644 index eca8e06805..0000000000 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/main/resources/update/echospot.update +++ /dev/null @@ -1,2 +0,0 @@ -1;ADD_CHANNEL;lastSpokenText,String,amazonechocontrol:lastSpokenText -2;ADD_CHANNEL;doNotDisturb,Switch,amazonechocontrol:doNotDisturb diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/GsonTypeAdapterFactoriesTest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/GsonTypeAdapterFactoriesTest.java new file mode 100644 index 0000000000..f020bb21eb --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/GsonTypeAdapterFactoriesTest.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.api.Test; +import org.smarthomej.binding.amazonechocontrol.internal.util.NonNullListTypeAdapterFactory; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNull; +import org.smarthomej.binding.amazonechocontrol.internal.util.SerializeNullTypeAdapterFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +/** + * The {@link GsonTypeAdapterFactoriesTest} contains tests for the various {@link TypeAdapterFactory} implementations + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@SuppressWarnings("unused") +public class GsonTypeAdapterFactoriesTest { + @Test + public void testSerializeNullFactoryReturnsNullForNonAnnotatedClass() { + TypeAdapterFactory factory = new SerializeNullTypeAdapterFactory(); + TypeToken typeToken = TypeToken.get(NonAnnotatedTestTO.class); + TypeAdapter typeAdapter = factory.create(new Gson(), typeToken); + + assertThat(typeAdapter, is(nullValue())); + } + + @Test + public void testSerializeNullFactoryReturnsTypeAdapterForAnnotatedClass() { + TypeAdapterFactory factory = new SerializeNullTypeAdapterFactory(); + TypeToken typeToken = TypeToken.get(AnnotatedTestTO.class); + TypeAdapter typeAdapter = factory.create(new Gson(), typeToken); + + assertThat(typeAdapter, is(notNullValue())); + } + + @Test + public void testNonNullListFactoryReturnsNullForNonListClass() { + TypeAdapterFactory factory = new NonNullListTypeAdapterFactory(); + TypeToken typeToken = TypeToken.get(String.class); + TypeAdapter typeAdapter = factory.create(new Gson(), typeToken); + + assertThat(typeAdapter, is(nullValue())); + } + + @Test + public void testNonNullListFactoryReturnsTypeAdapterForAnnotatedClass() { + TypeAdapterFactory factory = new NonNullListTypeAdapterFactory(); + TypeToken typeToken = TypeToken.getParameterized(List.class, String.class); + TypeAdapter typeAdapter = factory.create(new Gson(), typeToken); + + assertThat(typeAdapter, is(notNullValue())); + } + + @Test + public void testSerializeAnnotatedNull() { + Gson gson = new GsonBuilder().registerTypeAdapterFactory(new SerializeNullTypeAdapterFactory()).create(); + + String serialized = gson.toJson(new AnnotatedTestTO()); + String expected = "{\"annotatedNullValue\":null,\"annotatedNonNullValue\":\"bar\"," + + "\"nonNullValue\":\"foo\",\"serializedNameNonNullValue\":\"foo\"}"; + + assertThat(serialized, is(expected)); + } + + @Test + public void testNullListsAreNotDeserialized() { + Gson gson = new GsonBuilder().registerTypeAdapterFactory(new NonNullListTypeAdapterFactory()).create(); + + String in = "{\"list\" : [\"foo\"],\"nullList\" : null}"; + + ListTestTO listTest = Objects.requireNonNull(gson.fromJson(in, ListTestTO.class)); + assertThat(listTest.list, is(List.of("foo"))); + assertThat(listTest.nullList, is(List.of())); + assertThat(listTest.missingList, is(List.of())); + } + + @Test + public void combinedTest() { + String in = "{\"list\" : null}"; + + Gson gson = new GsonBuilder().registerTypeAdapterFactory(new SerializeNullTypeAdapterFactory()) + .registerTypeAdapterFactory(new NonNullListTypeAdapterFactory()).create(); + + CombinedTestTO combined = Objects.requireNonNull(gson.fromJson(in, CombinedTestTO.class)); + assertThat(combined.list, is(List.of())); + + String expected = "{\"serializeNullString\":null,\"list\":[]}"; + String out = gson.toJson(combined); + assertThat(out, is(expected)); + } + + private static class ListTestTO { + public List list = List.of(); + public List nullList = List.of(); + public List missingList = List.of(); + } + + private static class CombinedTestTO { + @SerializeNull + public @Nullable String serializeNullString = null; + public @Nullable String noSerializeNullString = null; + public List list = List.of(); + } + + private static class AnnotatedTestTO extends NonAnnotatedTestTO { + @SerializeNull + public @Nullable String annotatedNullValue = null; + + @SerializeNull + public @Nullable String annotatedNonNullValue = "bar"; + } + + private static class NonAnnotatedTestTO { + public @Nullable String nullValue = null; + + public String nonNullValue = "foo"; + + @SerializedName("serializedNameNullValue") + public @Nullable String nullValue2 = null; + + @SerializedName("serializedNameNonNullValue") + public String nonNullNullValue2 = "foo"; + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUriTest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUriTest.java new file mode 100644 index 0000000000..09e12b779c --- /dev/null +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/ServletUriTest.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlServlet.SERVLET_PATH; + +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * The {@link ServletUriTest} contains tests for the {@link ServletUri} record + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class ServletUriTest { + + private static Stream testGetStrippedUri() { + return Stream.of(Arguments.of(SERVLET_PATH, new ServletUri("", "")), // + Arguments.of(SERVLET_PATH + "/", new ServletUri("", "")), // + Arguments.of(SERVLET_PATH + "/accountUid", new ServletUri("accountUid", "")), // + Arguments.of(SERVLET_PATH + "/accountUid/", new ServletUri("accountUid", "")), // + Arguments.of(SERVLET_PATH + "/accountUid/foo/bar", new ServletUri("accountUid", "/foo/bar")), // + Arguments.of(SERVLET_PATH + "/accountUid/foo/bar/", new ServletUri("accountUid", "/foo/bar/")), // + Arguments.of("/foo/bar", null), // + Arguments.of(null, null)); + } + + @ParameterizedTest + @MethodSource + public void testGetStrippedUri(@Nullable String in, @Nullable ServletUri expected) { + assertThat(in, ServletUri.fromFullUri(in), is(expected)); + } +} diff --git a/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColorTest.java b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColorTest.java index 1d2486c784..a7c473a981 100644 --- a/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColorTest.java +++ b/bundles/org.smarthomej.binding.amazonechocontrol/src/test/java/org/smarthomej/binding/amazonechocontrol/internal/smarthome/AlexaColorTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.core.test.java.JavaTest; +import org.smarthomej.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; /** * The {@link AlexaColorTest} is a @@ -39,6 +40,6 @@ public void distanceTest(AlexaColor color) { @SuppressWarnings("unused") private static Stream getColors() { - return AlexaColor.ALEXA_COLORS.stream(); + return AmazonEchoControlBindingConstants.ALEXA_COLORS.stream(); } }