[amazonechocontrol] Import SmartHomeJ fork (#17935)
Both forks have diverged a reasonable amount. Signed-off-by: Cody Cutrer <cody@cutrer.us> Co-authored-by: Jan N. Klug <github@klug.nrw> Co-authored-by: Tom Blum <trinitus01@googlemail.com>pull/18293/head
parent
64eeeae32c
commit
f2e4492c31
|
@ -11,3 +11,5 @@ https://www.eclipse.org/legal/epl-2.0/.
|
|||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
Parts of this code have been forked from https://github.com/smarthomej/addons
|
|
@ -1,6 +1,22 @@
|
|||
# Amazon Echo Control Binding
|
||||
|
||||
This binding can control Amazon Echo devices (Alexa).
|
||||
This binding can control Amazon Echo devices (Alexa) and Smarthome devices connected through Alexa or a skill.
|
||||
|
||||
Upgrade notice:
|
||||
|
||||
- The `lastVoiceCommand` channel of the `amazonechocontrol` binding changed its behavior in version 5.0.0.
|
||||
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".
|
||||
- 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
|
||||
|
||||
It provides features to control and view the current state of echo devices:
|
||||
|
||||
|
@ -36,6 +52,7 @@ It also provides features to control devices connected to your echo:
|
|||
- control groups of lights or just single bulbs
|
||||
- receive the current state of the lights
|
||||
- turn on/off smart plugs (e. g. OSRAM)
|
||||
- receive states of attached sensors like particulate matter or carbon monoxide
|
||||
|
||||
Restrictions:
|
||||
|
||||
|
@ -86,8 +103,8 @@ With the possibility to control your lights you could do:
|
|||
|
||||
You must define an `account` (Bridge) before defining any other Thing can be used.
|
||||
|
||||
1. Create an 'Amazon Account' thing
|
||||
1. open the url YOUR_OPENHAB/amazonechocontrol in your browser (e.g. `http://openhab:8080/amazonechocontrol/`), click the link for your account thing and login.
|
||||
1. Create an Amazon `account` thing
|
||||
1. Open the url `YOUR_OPENHAB/amazonechocontrol` in your browser (e.g. http://openhab:8080/amazonechocontrol/), click the link for your account thing and login.
|
||||
1. You should see now a message that the login was successful
|
||||
1. If you encounter redirect/page refresh issues, enable two-factor authentication (2FA) on your Amazon account.
|
||||
|
||||
|
@ -108,11 +125,19 @@ See section *Smart Home Devices* below for more information.
|
|||
|
||||
### `account` Bridge 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
|
||||
|
||||
| Channel Type ID | Item Type | Access Mode | Thing Type | Description |
|
||||
|-----------------|-----------|-------------|------------|--------------------------------------------------|
|
||||
| `sendMessage` | String | W | account | Write Only! Sends a message to the Echo devices. |
|
||||
|
||||
|
||||
### Thing Configuration
|
||||
|
||||
|
@ -120,102 +145,64 @@ The `echo`, `echospot`, `echoshow` and `wha` have the same configuration:
|
|||
|
||||
| Configuration name | Description |
|
||||
|--------------------------|----------------------------------------------------|
|
||||
| serialNumber | Serial number of the Amazon Echo in the Alexa app |
|
||||
| `serialNumber` | Serial number of the Amazon Echo in the Alexa app |
|
||||
|
||||
You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/amazonechocontrol/YOUR_ACCOUNT (e.g. `http://openhab:8080/amazonechocontrol/account1`).
|
||||
You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/amazonechocontrol/YOUR_ACCOUNT (e.g. http://openhab:8080/amazonechocontrol/account1).
|
||||
|
||||
#### `flashbriefingprofile` Thing Configuration
|
||||
### Channels
|
||||
|
||||
The `flashbriefingprofile` has no configuration parameters.
|
||||
It will be configured at runtime by using the save channel to store the current flash briefing configuration in the thing. Create a `flashbriefingprofile` Thing for each set you need.
|
||||
E.g. One Flashbriefing profile with technical news and wheater, one for playing world news and one for sport news.
|
||||
| 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 |
|
||||
| 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.<FlahshbriefingDeviceID> (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. `<speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect></speak>` |
|
||||
| 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 |
|
||||
|
||||
#### `smartHomeDevice` and `smartHomeDeviceGroup` Thing Configuration
|
||||
## Advanced Feature Technically Experienced Users
|
||||
|
||||
| Configuration name | Description |
|
||||
|--------------------------|---------------------------------------------------------------------------|
|
||||
| id | The id of the device or device group |
|
||||
The url <YOUR_OPENHAB>/amazonechocontrol/<YOUR_ACCOUNT>/PROXY/<API_URL> provides a proxy server with an authenticated connection to the Amazon Alexa server.
|
||||
This can be used to call Alexa API from rules.
|
||||
|
||||
The only possibility to find out the id is by using the discover function in the UI. You can use then the id, if you want define the Thing in a file.
|
||||
E.g. to read out the history call from an installation on openhab:8080 with an account named account1:
|
||||
|
||||
1. Open the url YOUR_OPENHAB/amazonechocontrol in your browser (e.g. `http://openhab:8080/amazonechocontrol/`)
|
||||
1. Click on the name of the account thing
|
||||
1. Click on the name of the echo thing
|
||||
1. Scroll to the channel and copy the required ID
|
||||
http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1
|
||||
|
||||
## Channels
|
||||
### Example
|
||||
|
||||
| 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 | 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 | Speak the reminder and sends a notification to the Alexa app |
|
||||
| nextReminder | DateTime | R | echo, echoshow, echospot | Next reminder on the device |
|
||||
| playAlarmSound | String | W | echo, echoshow, echospot | 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 | 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 | Voice command as text. E.g. 'Yesterday from the Beatles' |
|
||||
| startCommand | String | W | echo, echoshow, echospot | Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing.\<FlahshbriefingDeviceID> (Note: The options are case sensitive) |
|
||||
| announcement | String | W | echo, echoshow, echospot | 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 some plain text to this channel and Alexa will speak it. SSML is also possible: e.g. `<speak>I want to tell you a secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect></speak>` |
|
||||
| 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 | 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. |
|
||||
| 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 |
|
||||
| sendMessage | String | W | account | Sends a message to the Echo devices. |
|
||||
| save | Switch | W | flashbriefingprofile | 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. |
|
||||
| powerState | Switch | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the state (ON/OFF) of your device |
|
||||
| brightness | Dimmer | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the brightness of your lamp |
|
||||
| color | Color | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows the color of your light |
|
||||
| colorName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the color name of your light (groups are not able to show their color) |
|
||||
| colorTemperatureInKelvin | Number:Temperature | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows the color temperature of your light |
|
||||
| colorTemperatureName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | White temperatures name of your lights (groups are not able to show their color) |
|
||||
| armState | String | R/W | smartHomeDevice, smartHomeDeviceGroup | State of your alarm guard. Options: ARMED_AWAY, ARMED_STAY, ARMED_NIGHT, DISARMED (groups are not able to show their state) |
|
||||
| burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm |
|
||||
| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm |
|
||||
| fireAlarm | Contact | R | smartHomeDevice | Fire alarm |
|
||||
| waterAlarm | Contact | R | smartHomeDevice | Water alarm |
|
||||
| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm |
|
||||
| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm |
|
||||
| temperature | Number:Temperature | R | smartHomeDevice | Temperature |
|
||||
| targetSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat target setpoint |
|
||||
| upperSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat upper setpoint (AUTO) |
|
||||
| lowerSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat lower setpoint (AUTO) |
|
||||
| relativeHumidity | Number:Dimensionless | R | smartHomeDevice | Thermostat humidity |
|
||||
| thermostatMode | String | R/W | smartHomeDevice | Thermostat operation mode |
|
||||
|
||||
*note* the channels of `smartHomeDevices` and `smartHomeDeviceGroup` will be created dynamically based on the capabilities reported by the amazon server. This can take a little bit of time.
|
||||
The polling interval configured in the Account Thing to get the state is specified in minutes and has a minimum of 10. This means it takes up to 10 minutes to see the state of a channel. The reason for this low interval is, that the polling causes a big server load for the Smart Home Skills.
|
||||
|
||||
## Full Example
|
||||
|
||||
### echo.things
|
||||
#### echo.things
|
||||
|
||||
```java
|
||||
Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discoverSmartHome=2, pollingIntervalSmartHomeAlexa=30, pollingIntervalSmartSkills=120]
|
||||
|
@ -229,8 +216,7 @@ Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discove
|
|||
Thing smartHomeDevice smartHomeDevice1 "Smart Home Device 1" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDevice smartHomeDevice2 "Smart Home Device 2" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDevice smartHomeDevice3 "Smart Home Device 3" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDeviceGroup smartHomeDeviceGroup1 "Living Room Group" @ "Living Room" [id="ID"]
|
||||
}
|
||||
Thing smartHomeDeviceGroup smartHomeDeviceGroup1 "Living Room Group" @ "Living Room" [id="ID"]}
|
||||
```
|
||||
|
||||
#### echo.items
|
||||
|
@ -240,96 +226,63 @@ Take a look in the channel description above to know, which channels are support
|
|||
|
||||
```java
|
||||
// Account
|
||||
String Echo_Living_Room_SendMessage "SendMessage" {channel="amazonechocontrol:account:account1:sendMessage"}
|
||||
String Echo_Living_Room_SendMessage "SendMessage" {channel="amazonechocontrol:account:account1:sendMessage"}
|
||||
|
||||
Group Alexa_Living_Room <player>
|
||||
|
||||
// Player control
|
||||
Player Echo_Living_Room_Player "Player" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:player"}
|
||||
Dimmer Echo_Living_Room_Volume "Volume [%.0f %%]" <soundvolume> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:volume"}
|
||||
Number Echo_Living_Room_Treble "Treble" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerTreble"}
|
||||
Number Echo_Living_Room_Midrange "Midrange" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerMidrange"}
|
||||
Number Echo_Living_Room_Bass "Bass" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerBass"}
|
||||
Switch Echo_Living_Room_Shuffle "Shuffle" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:shuffle"}
|
||||
Player Echo_Living_Room_Player "Player" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:player"}
|
||||
Dimmer Echo_Living_Room_Volume "Volume [%.0f %%]" <soundvolume> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:volume"}
|
||||
Number Echo_Living_Room_Treble "Treble" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerTreble"}
|
||||
Number Echo_Living_Room_Midrange "Midrange" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerMidrange"}
|
||||
Number Echo_Living_Room_Bass "Bass" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:equalizerBass"}
|
||||
Switch Echo_Living_Room_Shuffle "Shuffle" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:shuffle"}
|
||||
|
||||
// Media channels
|
||||
Dimmer Echo_Living_Room_MediaProgress "Media progress" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaProgress"}
|
||||
Number:Time Echo_Living_Room_MediaProgressTime "Media progress time [%d %unit%]" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaProgressTime"}
|
||||
Number:Time Echo_Living_Room_MediaLength "Media length [%d %unit%]" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaLength"}
|
||||
Dimmer Echo_Living_Room_MediaProgress "Media progress" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaProgress"}
|
||||
Number:Time Echo_Living_Room_MediaProgressTime "Media progress time [%d %unit%]" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaProgressTime"}
|
||||
Number:Time Echo_Living_Room_MediaLength "Media length [%d %unit%]" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:mediaLength"}
|
||||
|
||||
// Player Information
|
||||
String Echo_Living_Room_ImageUrl "Image URL" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:imageUrl"}
|
||||
String Echo_Living_Room_Title "Title" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:title"}
|
||||
String Echo_Living_Room_Subtitle1 "Subtitle 1" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:subtitle1"}
|
||||
String Echo_Living_Room_Subtitle2 "Subtitle 2" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:subtitle2"}
|
||||
String Echo_Living_Room_ProviderDisplayName "Provider" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:providerDisplayName"}
|
||||
String Echo_Living_Room_ImageUrl "Image URL" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:imageUrl"}
|
||||
String Echo_Living_Room_Title "Title" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:title"}
|
||||
String Echo_Living_Room_Subtitle1 "Subtitle 1" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:subtitle1"}
|
||||
String Echo_Living_Room_Subtitle2 "Subtitle 2" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:subtitle2"}
|
||||
String Echo_Living_Room_ProviderDisplayName "Provider" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:providerDisplayName"}
|
||||
|
||||
// Music provider and start command
|
||||
String Echo_Living_Room_MusicProviderId "Music Provider Id" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:musicProviderId"}
|
||||
String Echo_Living_Room_PlayMusicCommand "Play music voice command (Write Only)" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:playMusicVoiceCommand"}
|
||||
String Echo_Living_Room_StartCommand "Start Information" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:startCommand"}
|
||||
|
||||
// TuneIn Radio
|
||||
String Echo_Living_Room_RadioStationId "TuneIn Radio Station Id" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:radioStationId"}
|
||||
Switch Echo_Living_Room_Radio "TuneIn Radio" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:radio"}
|
||||
|
||||
// Amazon Music
|
||||
String Echo_Living_Room_AmazonMusicTrackId "Amazon Music Track Id" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:amazonMusicTrackId"}
|
||||
String Echo_Living_Room_AmazonMusicPlayListId "Amazon Music Playlist Id" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:amazonMusicPlayListId"}
|
||||
Switch Echo_Living_Room_AmazonMusic "Amazon Music" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:amazonMusic"}
|
||||
String Echo_Living_Room_MusicProviderId "Music Provider Id" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:musicProviderId"}
|
||||
String Echo_Living_Room_PlayMusicCommand "Play music voice command (Write Only)" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:playMusicVoiceCommand"}
|
||||
String Echo_Living_Room_StartCommand "Start Information" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:startCommand"}
|
||||
|
||||
// Bluetooth
|
||||
String Echo_Living_Room_BluetoothMAC "Bluetooth MAC Address" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetoothMAC"}
|
||||
Switch Echo_Living_Room_Bluetooth "Bluetooth" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetooth"}
|
||||
String Echo_Living_Room_BluetoothDeviceName "Bluetooth Device" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetoothDeviceName"}
|
||||
String Echo_Living_Room_BluetoothMAC "Bluetooth MAC Address" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetoothMAC"}
|
||||
Switch Echo_Living_Room_Bluetooth "Bluetooth" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetooth"}
|
||||
String Echo_Living_Room_BluetoothDeviceName "Bluetooth Device" <bluetooth> (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:bluetoothDeviceName"}
|
||||
|
||||
// Commands
|
||||
String Echo_Living_Room_Announcement "Announcement" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:announcement"}
|
||||
String Echo_Living_Room_TTS "Text to Speech" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:textToSpeech"}
|
||||
Dimmer Echo_Living_Room_TTS_Volume "Text to Speech Volume" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:textToSpeechVolume"}
|
||||
String Echo_Living_Room_Remind "Remind" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:remind"}
|
||||
String Echo_Living_Room_PlayAlarmSound "Play Alarm Sound" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:playAlarmSound"}
|
||||
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"}
|
||||
String Echo_Living_Room_Announcement "Announcement" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:announcement"}
|
||||
String Echo_Living_Room_TTS "Text to Speech" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:textToSpeech"}
|
||||
Dimmer Echo_Living_Room_TTS_Volume "Text to Speech Volume" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:textToSpeechVolume"}
|
||||
String Echo_Living_Room_Remind "Remind" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:remind"}
|
||||
String Echo_Living_Room_PlayAlarmSound "Play Alarm Sound" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:playAlarmSound"}
|
||||
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"}
|
||||
|
||||
// Feedbacks
|
||||
String Echo_Living_Room_LastVoiceCommand "Last voice command" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:lastVoiceCommand"}
|
||||
DateTime Echo_Living_Room_NextReminder "Next reminder" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextReminder"}
|
||||
DateTime Echo_Living_Room_NextAlarm "Next alarm" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextAlarm"}
|
||||
DateTime Echo_Living_Room_NextMusicAlarm "Next music alarm" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextMusicAlarm"}
|
||||
DateTime Echo_Living_Room_NextTimer "Next timer" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextTimer"}
|
||||
|
||||
// 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"}
|
||||
|
||||
// Lights and lightgroups
|
||||
Switch Light_State "On/Off" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:powerState"}
|
||||
Dimmer Light_Brightness "Brightness" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:brightness"}
|
||||
Color Light_Color "Color" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:color"}
|
||||
String Light_Color_Name "Color Name" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:colorName"}
|
||||
String Light_White "White temperature" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:colorTemperatureName"}
|
||||
|
||||
// Smart plugs
|
||||
Switch Plug_State "On/Off" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice2:powerState"}
|
||||
|
||||
// Alexa Guard
|
||||
Switch Arm_State "State" {channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice3:armState"}
|
||||
|
||||
// Smart Home device group
|
||||
Switch Group_State "On/Off" {channel="amazonechocontrol:smartHomeDeviceGroup:account1:smartHomeDeviceGroup1:powerState"}
|
||||
String Echo_Living_Room_LastVoiceCommand "Last voice command" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:lastVoiceCommand"}
|
||||
String Echo_Living_Room_LastSpokenText "Last spoken text" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:lastSpokenText"}
|
||||
DateTime Echo_Living_Room_NextReminder "Next reminder" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextReminder"}
|
||||
DateTime Echo_Living_Room_NextAlarm "Next alarm" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextAlarm"}
|
||||
DateTime Echo_Living_Room_NextMusicAlarm "Next music alarm" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextMusicAlarm"}
|
||||
DateTime Echo_Living_Room_NextTimer "Next timer" (Alexa_Living_Room) {channel="amazonechocontrol:echo:account1:echo1:nextTimer"}
|
||||
```
|
||||
|
||||
#### echo.sitemap
|
||||
|
||||
```perl
|
||||
sitemap amazonechocontrol label="Amazone Devices"
|
||||
```java
|
||||
sitemap amazonechocontrol label="Echo Devices"
|
||||
{
|
||||
Frame label="Alexa" {
|
||||
Default item=Echo_Living_Room_Player
|
||||
|
@ -355,14 +308,6 @@ sitemap amazonechocontrol label="Amazone Devices"
|
|||
// To start one of your flashbriefings use Flashbriefing.<YOUR FLASHBRIEFING THING ID>
|
||||
Selection item=Echo_Living_Room_StartCommand mappings=[ 'Weather'='Weather', 'Traffic'='Traffic', 'GoodMorning'='Good Morning', 'SingASong'='Song', 'TellStory'='Story', 'FlashBriefing'='Flash Briefing', 'FlashBriefing.flashbriefing1'='Technical', 'FlashBriefing.flashbriefing2'='Life Style' ]
|
||||
|
||||
Selection item=Echo_Living_Room_RadioStationId mappings=[ ''='Off', 's1139'='Antenne Steiermark', 's8007'='Hitradio Ö3', 's16793'='Radio 10', 's8235'='FM4' ]
|
||||
Text item=Echo_Living_Room_RadioStationId
|
||||
Switch item=Echo_Living_Room_Radio
|
||||
|
||||
Text item=Echo_Living_Room_AmazonMusicTrackId
|
||||
Text item=Echo_Living_Room_AmazonMusicPlayListId
|
||||
Switch item=Echo_Living_Room_AmazonMusic
|
||||
|
||||
Text item=Echo_Living_Room_BluetoothMAC
|
||||
// Change the <YOUR_DEVICE_MAC> Place holder with the MAC address shown, if Alexa is connected to the device
|
||||
Selection item=Echo_Living_Room_BluetoothMAC mappings=[ ''='Disconnected', '<YOUR_DEVICE_MAC>'='Bluetooth Device 1', '<YOUR_DEVICE_MAC>'='Bluetooth Device 2']
|
||||
|
@ -373,10 +318,78 @@ sitemap amazonechocontrol label="Amazone Devices"
|
|||
Switch item=Echo_Living_Room_Bluetooth
|
||||
Text item=Echo_Living_Room_BluetoothDeviceName
|
||||
Text item=Echo_Living_Room_LastVoiceCommand
|
||||
Text item=Echo_Living_Room_LastSpokenText
|
||||
Slider item=Echo_Living_Room_NotificationVolume
|
||||
Switch item=Echo_Living_Room_AscendingAlarm
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `flashbriefingprofile` Thing Configuration
|
||||
|
||||
The `flashbriefingprofile` has no configuration parameters.
|
||||
It will be configured at runtime by using the save channel to store the current flash briefing configuration in the thing. Create a `flashbriefingprofile` Thing for each set you need.
|
||||
E.g. One Flashbriefing profile with technical news and wheater, one for playing world news and one for sport news.
|
||||
|
||||
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
|
||||
|
||||
| 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. |
|
||||
|
||||
**Attention:** Be careful when using the `save` channel.
|
||||
Storing the same configuration to several things may result in unpredictable behavior.
|
||||
|
||||
### Example
|
||||
|
||||
#### flashbriefings.things
|
||||
|
||||
```java
|
||||
Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discoverSmartHome=2]
|
||||
{
|
||||
Thing flashbriefingprofile flashbriefing1 "Flash Briefing Technical" @ "Flash Briefings"
|
||||
Thing flashbriefingprofile flashbriefing2 "Flash Briefing Life Style" @ "Flash Briefings"
|
||||
}
|
||||
```
|
||||
|
||||
#### flashbriefings.items
|
||||
|
||||
```java
|
||||
// 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"}
|
||||
```
|
||||
|
||||
#### flashbriefings.sitemap
|
||||
|
||||
```java
|
||||
sitemap flashbriefings label="Flash Briefings"
|
||||
{
|
||||
Frame label="Flash Briefing Technical" {
|
||||
Switch item=FlashBriefing_Technical_Save
|
||||
Switch item=FlashBriefing_Technical_Active
|
||||
|
@ -388,7 +401,106 @@ sitemap amazonechocontrol label="Amazone Devices"
|
|||
Switch item=FlashBriefing_LifeStyle_Active
|
||||
Text item=FlashBriefing_LifeStyle_Play
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `smartHomeDevice` and `smartHomeDeviceGroup` Thing Configuration
|
||||
|
||||
| Configuration name | Description |
|
||||
|--------------------------|---------------------------------------------------------------------------|
|
||||
| id | The id of the device or device group |
|
||||
|
||||
The only possibility to find out the id is using discovery function in the UI.
|
||||
You can use that id if you want to define the thing in a file.
|
||||
|
||||
1. Open the url YOUR_OPENHAB/amazonechocontrol in your browser (e.g. `http://openhab:8080/amazonechocontrol/`)
|
||||
1. Click on the name of the account thing
|
||||
1. Click on the name of the echo thing
|
||||
1. Scroll to the channel and copy the required ID
|
||||
|
||||
Discovered smart home devices show a `deviceIdentifierList` in their thing properties, containing one or more serial numbers.
|
||||
You can check if any of these serial numbers is associated with another device and use this to identify devices with similar/same names.
|
||||
|
||||
### Channels
|
||||
|
||||
The channels of the smarthome devices will be generated at runtime.
|
||||
Check in the UI thing configurations, which channels are created.
|
||||
|
||||
| Channel ID | Item Type | Access Mode | Thing Type | Description |
|
||||
|--------------------------|----------------------|-------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||
| powerState | Switch | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the state (ON/OFF) of your device |
|
||||
| brightness | Dimmer | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the brightness of your lamp |
|
||||
| color | Color | R/(W) | smartHomeDevice, smartHomeDeviceGroup | Shows the color of your light |
|
||||
| colorName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the color name of your light (groups are not able to show their color) |
|
||||
| colorTemperatureInKelvin | Number:Temperature | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows the color temperature of your light |
|
||||
| colorTemperatureName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | White temperatures name of your lights (groups are not able to show their color) |
|
||||
| armState | String | R/W | smartHomeDevice, smartHomeDeviceGroup | State of your alarm guard. Options: ARMED_AWAY, ARMED_STAY, ARMED_NIGHT, DISARMED (groups are not able to show their state) |
|
||||
| burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm |
|
||||
| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm |
|
||||
| fireAlarm | Contact | R | smartHomeDevice | Fire alarm |
|
||||
| waterAlarm | Contact | R | smartHomeDevice | Water alarm |
|
||||
| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm |
|
||||
| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm |
|
||||
| temperature | Number | R | smartHomeDevice | Temperature |
|
||||
| targetSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat Setpoint |
|
||||
| upperSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat Upper Setpoint |
|
||||
| lowerSetpoint | Number:Temperature | R/W | smartHomeDevice | Thermostat Lower Setpoint |
|
||||
| relativeHumidity | Number:Dimensionless | R | smartHomeDevice | Thermostat humidity |
|
||||
| thermostatMode | String | R/W | smartHomeDevice | Thermostat Mode (`AUTO`, `COOL`, `HEAT`, `OFF`, `ECO`) |
|
||||
| motionDetected | Switch | R | smartHomeDevice | A motion was detected if ON |
|
||||
| contact | Contact | R | smartHomeDevice | A contact sensor OPEN if detected, CLOSED if NOT_DETECTED |
|
||||
| geoLocation | Location | R | smartHomeDevice | The location (e.g. of a Tile) |
|
||||
|
||||
*Note* the channels of `smartHomeDevices` and `smartHomeDeviceGroup` will be created dynamically based on the capabilities reported by the Amazon server. This can take a little bit of time.
|
||||
The polling interval configured in the Account Thing to get the state is specified in minutes and has a minimum of 10. This means it takes up to 10 minutes to see the state of a channel. The reason for this low interval is, that the polling causes a big server load for the Smart Home Skills.
|
||||
|
||||
*Note*: The `color` channel is read-only by default because Alexa does only support setting colors by their name.
|
||||
It has a configuration parameter `matchColors` which enables writing to that channel and tries to find the closes available color when sending a command to Alexa.
|
||||
|
||||
### Example
|
||||
|
||||
#### smarthome.things
|
||||
|
||||
```java
|
||||
Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discoverSmartHome=2, pollingIntervalSmartHomeAlexa=30, pollingIntervalSmartSkills=120]
|
||||
{
|
||||
Thing smartHomeDevice smartHomeDevice1 "Smart Home Device 1" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDevice smartHomeDevice2 "Smart Home Device 2" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDevice smartHomeDevice3 "Smart Home Device 3" @ "Living Room" [id="ID"]
|
||||
Thing smartHomeDeviceGroup smartHomeDeviceGroup1 "Living Room Group" @ "Living Room" [id="ID"]
|
||||
}
|
||||
```
|
||||
|
||||
#### smarthome.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.
|
||||
|
||||
```java
|
||||
// Lights and lightgroups
|
||||
Switch Light_State "On/Off" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:powerState" }
|
||||
Dimmer Light_Brightness "Brightness" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:brightness" }
|
||||
Color Light_Color "Color" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:color" }
|
||||
String Light_Color_Name "Color Name" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:colorName" }
|
||||
String Light_White "White temperature" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice1:colorTemperatureName" }
|
||||
|
||||
// Smart plugs
|
||||
Switch Plug_State "On/Off" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice2:powerState" }
|
||||
|
||||
// Alexa Guard
|
||||
Switch Arm_State "State" { channel="amazonechocontrol:smartHomeDevice:account1:smartHomeDevice3:armState" }
|
||||
|
||||
// Smart Home device group
|
||||
Switch Group_State "On/Off" { channel="amazonechocontrol:smartHomeDeviceGroup:account1:smartHomeDeviceGroup1:powerState" }
|
||||
```
|
||||
|
||||
The only possibility to find out the id for the smartHomeDevice and smartHomeDeviceGroup Things is by using the discover function.
|
||||
|
||||
#### smarthome.sitemap
|
||||
|
||||
```java
|
||||
sitemap smarthome label="Smart Home Devices"
|
||||
{
|
||||
Frame label="Lights and light groups" {
|
||||
Switch item=Light_State
|
||||
Slider item=Light_Brightness
|
||||
|
@ -402,6 +514,17 @@ sitemap amazonechocontrol label="Amazone Devices"
|
|||
}
|
||||
```
|
||||
|
||||
#### Rule for calculating the distance of a Tile from your home
|
||||
|
||||
Link the `geoLocation` channel of the Tile thing to a `Location` item named `CarLocation`.
|
||||
Add a second item of type `Number:Length` with the name `CarDistance` (adjust state description to your needs, e.g. miles or km as unit).
|
||||
Create a rule that triggers on change of that item with the DSL script as action:
|
||||
|
||||
```
|
||||
var homeLocation = new PointType("50.273448, 8.409950")
|
||||
CarDistance.postUpdate(homeLocation.distanceFrom(CarLocation.state as PointType).toString + " m")
|
||||
```
|
||||
|
||||
## Advanced Feature Technically Experienced Users
|
||||
|
||||
The url <YOUR_OPENHAB>/amazonechocontrol/<YOUR_ACCOUNT>/PROXY/<API_URL> provides a proxy server with an authenticated connection to the Amazon Alexa server.
|
||||
|
@ -409,7 +532,7 @@ This can be used to call Alexa API from rules.
|
|||
|
||||
E.g. to read out the history call from an installation on openhab:8080 with an account named account1:
|
||||
|
||||
`http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1`
|
||||
http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1
|
||||
|
||||
To resolve login problems the connection settings of an `account` thing can be reset via the karaf console.
|
||||
The command `amazonechocontrol listAccounts` shows a list of all available `account` things.
|
||||
|
@ -462,7 +585,7 @@ Expert:
|
|||
You can use a json formatted string to control title, sound and volume:
|
||||
|
||||
```json
|
||||
{"sound": true, "speak":"<Speak>", "title": "<Title>", "body": "<Body Text>", "volume": 20}
|
||||
{ "sound": true, "speak":"<Speak>", "title": "<Title>", "body": "<Body Text>", "volume": 20}
|
||||
```
|
||||
|
||||
The combination of `sound=true` and `speak` in SSML syntax is not allowed.
|
||||
|
@ -473,20 +596,21 @@ No specification uses the volume from the `textToSpeechVolume` channel.
|
|||
|
||||
Note: If you turn off the sound and Alexa is playing music, it will anyway turn down the volume for a moment. This behavior can not be changed.
|
||||
|
||||
|
||||
```java
|
||||
rule "Say welcome if the door opens"
|
||||
when
|
||||
Item Door_Contact changed to OPEN
|
||||
then
|
||||
Echo_Living_Room_Announcement.sendCommand('{"sound": false, "title": "Doorstep", "body": "Door opened"}')
|
||||
Echo_Living_Room_Announcement.sendCommand('{ "sound": false, "title": "Doorstep", "body": "Door opened"}')
|
||||
end
|
||||
```
|
||||
|
||||
## Playing an alarm sound for 15 seconds with an openHAB rule if a door contact was opened
|
||||
|
||||
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
1. Write down the text in the square brackets. e.g. ECHO:system_alerts_repetitive01 for the nightstand sound
|
||||
1. Create a rule for start playing the sound:
|
||||
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
2) Write down the text in the square brackets. e.g. ECHO:system_alerts_repetitive01 for the nightstand sound
|
||||
3) Create a rule for start playing the sound:
|
||||
|
||||
```java
|
||||
var Timer stopAlarmTimer = null
|
||||
|
@ -514,9 +638,9 @@ Note 2: The rule have no effect for your default alarm sound used in the Alexa a
|
|||
|
||||
### Play a spotify playlist if a switch was changed to on
|
||||
|
||||
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
1. Write down the text in the square brackets. e.g. SPOTIFY for the spotify music provider
|
||||
1. Create a rule for start playing a song or playlist:
|
||||
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
2) Write down the text in the square brackets. e.g. SPOTIFY for the spotify music provider
|
||||
3) Create a rule for start playing a song or playlist:
|
||||
|
||||
```java
|
||||
rule "Play a playlist on spotify if a switch was changed"
|
||||
|
@ -532,8 +656,8 @@ Note: It is recommended to test the command send to play music command first wit
|
|||
|
||||
### Start playing weather/traffic/etc
|
||||
|
||||
1. Pick up one of the available commands: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing
|
||||
1. Create a rule for start playing the information where you provide the command as string:
|
||||
1) Pick up one of the available commands: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing
|
||||
2) Create a rule for start playing the information where you provide the command as string:
|
||||
|
||||
```java
|
||||
rule "Start wheater info"
|
||||
|
@ -546,9 +670,9 @@ end
|
|||
|
||||
### Start playing a custom flashbriefing on a device
|
||||
|
||||
1. Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
1. Write down the text in the square brackets. e.g. flashbriefing.flashbriefing1
|
||||
1. Create a rule for start playing the information where you provide the command as string:
|
||||
1) Do get the ID of your sound, follow the steps in "How To Get IDs"
|
||||
2) Write down the text in the square brackets. e.g. flashbriefing.flashbriefing1
|
||||
2) Create a rule for start playing the information where you provide the command as string:
|
||||
|
||||
```java
|
||||
rule "Start wheater info"
|
||||
|
@ -571,7 +695,8 @@ The binding is tested with amazon.de, amazon.fr, amazon.it, amazon.com and amazo
|
|||
|
||||
The idea for writing this binding came from this blog: [https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html](https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html) (German).
|
||||
Thank you Alex!
|
||||
The technical information for the web socket connection to get live Alexa state updates cames from Ingo. He has done the Alexa ioBroker implementation [https://github.com/Apollon77](https://github.com/Apollon77)
|
||||
The technical information for the web socket connection to get live Alexa state updates cames from Ingo.
|
||||
He has done the Alexa ioBroker implementation https://github.com/Apollon77
|
||||
Thank you Ingo!
|
||||
|
||||
## Trademark Disclaimer
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
<version>1.1.6.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>2.3</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<feature name="openhab-binding-amazonechocontrol" description="Amazon Echo Control Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle dependency="true">mvn:org.apache.velocity/velocity-engine-core/2.3</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.amazonechocontrol/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
|
|
|
@ -25,4 +25,5 @@ public class AccountHandlerConfig {
|
|||
public int discoverSmartHome = 0;
|
||||
public int pollingIntervalSmartHomeAlexa = 60;
|
||||
public int pollingIntervalSmartSkills = 120;
|
||||
public int activityRequestDelay = 10;
|
||||
}
|
||||
|
|
|
@ -1,719 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.openhab.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.Hashtable;
|
||||
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.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
Hashtable<Object, Object> initParams = new Hashtable<>();
|
||||
initParams.put("servlet-name", servletUrl);
|
||||
|
||||
httpService.registerServlet(servletUrl, this, initParams, 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<String, String[]> 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.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<String, String[]> 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.name()));
|
||||
}
|
||||
|
||||
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 | InterruptedException e) {
|
||||
logger.warn("get failed with uri syntax error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getQueryMap(@Nullable String query) {
|
||||
Map<String, String> 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("<form action='");
|
||||
html.append(servletUrl);
|
||||
html.append("/changedomain' method='post'>\nDomain:\n<input type='text' name='domain' value='");
|
||||
html.append(connection.getAmazonSite());
|
||||
html.append("'>\n<br>\n<input type=\"submit\" value=\"Submit\">\n</form>");
|
||||
|
||||
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(" <a href='" + servletUrl + "/logout' >");
|
||||
html.append(HtmlEscape.escapeHtml4("Logout"));
|
||||
html.append("</a>");
|
||||
// newdevice link
|
||||
html.append(" | <a href='" + servletUrl + "/newdevice' >");
|
||||
html.append(HtmlEscape.escapeHtml4("Logout and create new device id"));
|
||||
html.append("</a>");
|
||||
// customer id
|
||||
html.append("<br>Customer Id: ");
|
||||
html.append(HtmlEscape.escapeHtml4(connection.getCustomerId()));
|
||||
// customer name
|
||||
html.append("<br>Customer Name: ");
|
||||
html.append(HtmlEscape.escapeHtml4(connection.getCustomerName()));
|
||||
// device name
|
||||
html.append("<br>App name: ");
|
||||
html.append(HtmlEscape.escapeHtml4(connection.getDeviceName()));
|
||||
// connection
|
||||
html.append("<br>Connected to: ");
|
||||
html.append(HtmlEscape.escapeHtml4(connection.getAlexaServer()));
|
||||
// domain
|
||||
html.append(" <a href='");
|
||||
html.append(servletUrl);
|
||||
html.append("/changeDomain'>Change</a>");
|
||||
|
||||
// Main UI link
|
||||
html.append("<br><a href='/#!/settings/things/" + BINDING_ID + ":"
|
||||
+ URLEncoder.encode(THING_TYPE_ACCOUNT.getId(), "UTF8") + ":" + URLEncoder.encode(id, "UTF8") + "'>");
|
||||
html.append(HtmlEscape.escapeHtml4("Check Thing in Main UI"));
|
||||
html.append("</a><br><br>");
|
||||
|
||||
// device list
|
||||
html.append(
|
||||
"<table><tr><th align='left'>Device</th><th align='left'>Serial Number</th><th align='left'>State</th><th align='left'>Thing</th><th align='left'>Family</th><th align='left'>Type</th><th align='left'>Customer Id</th></tr>");
|
||||
for (Device device : this.account.getLastKnownDevices()) {
|
||||
|
||||
html.append("<tr><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(device.accountName)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(device.serialNumber)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(device.online ? "Online" : "Offline"));
|
||||
html.append("</td><td>");
|
||||
Thing accountHandler = account.findThingBySerialNumber(device.serialNumber);
|
||||
if (accountHandler != null) {
|
||||
html.append("<a href='" + servletUrl + "/ids/?serialNumber="
|
||||
+ URLEncoder.encode(device.serialNumber, "UTF8") + "'>"
|
||||
+ HtmlEscape.escapeHtml4(accountHandler.getLabel()) + "</a>");
|
||||
} else {
|
||||
html.append("<a href='" + servletUrl + "/ids/?serialNumber="
|
||||
+ URLEncoder.encode(device.serialNumber, "UTF8") + "'>" + HtmlEscape.escapeHtml4("Not defined")
|
||||
+ "</a>");
|
||||
}
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceFamily)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceType)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(device.deviceOwnerCustomerId)));
|
||||
html.append("</td>");
|
||||
html.append("</tr>");
|
||||
}
|
||||
html.append("</table>");
|
||||
createPageEndAndSent(resp, html);
|
||||
}
|
||||
|
||||
private void handleDevices(HttpServletResponse resp, Connection connection)
|
||||
throws IOException, URISyntaxException, InterruptedException {
|
||||
returnHtml(connection, resp, "<html>" + HtmlEscape.escapeHtml4(connection.getDeviceListJson()) + "</html>");
|
||||
}
|
||||
|
||||
private String nullReplacement(@Nullable String text) {
|
||||
if (text == null) {
|
||||
return "<unknown>";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
StringBuilder createPageStart(String title) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append("<html><head><title>"
|
||||
+ HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel()));
|
||||
if (!title.isEmpty()) {
|
||||
html.append(" - ");
|
||||
html.append(HtmlEscape.escapeHtml4(title));
|
||||
}
|
||||
html.append("</title><head><body>");
|
||||
html.append("<h1>" + HtmlEscape.escapeHtml4(BINDING_NAME + " - " + this.account.getThing().getLabel()));
|
||||
if (!title.isEmpty()) {
|
||||
html.append(" - ");
|
||||
html.append(HtmlEscape.escapeHtml4(title));
|
||||
}
|
||||
html.append("</h1>");
|
||||
return html;
|
||||
}
|
||||
|
||||
private void createPageEndAndSent(HttpServletResponse resp, StringBuilder html) {
|
||||
// account overview link
|
||||
html.append("<br><a href='" + servletUrl + "/../' >");
|
||||
html.append(HtmlEscape.escapeHtml4("Account overview"));
|
||||
html.append("</a><br>");
|
||||
|
||||
html.append("</body></html>");
|
||||
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("<h2>Capabilities</h2>");
|
||||
html.append("<table><tr><th align='left'>Name</th></tr>");
|
||||
device.getCapabilities().forEach(
|
||||
capability -> html.append("<tr><td>").append(HtmlEscape.escapeHtml4(capability)).append("</td></tr>"));
|
||||
html.append("</table>");
|
||||
}
|
||||
|
||||
private void renderMusicProviderIdChannel(Connection connection, StringBuilder html) {
|
||||
html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_MUSIC_PROVIDER_ID)).append("</h2>");
|
||||
html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
|
||||
List<JsonMusicProvider> musicProviders = connection.getMusicProviders();
|
||||
for (JsonMusicProvider musicProvider : musicProviders) {
|
||||
List<String> 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("<tr><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(displayName));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(providerId));
|
||||
html.append("</td></tr>");
|
||||
}
|
||||
}
|
||||
html.append("</table>");
|
||||
}
|
||||
|
||||
private void renderPlayAlarmSoundChannel(Connection connection, Device device, StringBuilder html) {
|
||||
html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_PLAY_ALARM_SOUND)).append("</h2>");
|
||||
List<JsonNotificationSound> notificationSounds = List.of();
|
||||
String errorMessage = "No notifications sounds found";
|
||||
try {
|
||||
notificationSounds = connection.getNotificationSounds(device);
|
||||
} catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
|
||||
| InterruptedException e) {
|
||||
errorMessage = e.getLocalizedMessage();
|
||||
}
|
||||
if (!notificationSounds.isEmpty()) {
|
||||
html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
|
||||
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("<tr><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(notificationSound.displayName));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(providerSoundId));
|
||||
html.append("</td></tr>");
|
||||
}
|
||||
}
|
||||
html.append("</table>");
|
||||
} else {
|
||||
html.append(HtmlEscape.escapeHtml4(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device device, StringBuilder html) {
|
||||
html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_AMAZON_MUSIC_PLAY_LIST_ID))
|
||||
.append("</h2>");
|
||||
|
||||
JsonPlaylists playLists = null;
|
||||
String errorMessage = "No playlists found";
|
||||
try {
|
||||
playLists = connection.getPlaylists(device);
|
||||
} catch (IOException | HttpException | URISyntaxException | JsonSyntaxException | ConnectionException
|
||||
| InterruptedException e) {
|
||||
errorMessage = e.getLocalizedMessage();
|
||||
}
|
||||
|
||||
if (playLists != null) {
|
||||
Map<String, PlayList @Nullable []> playlistMap = playLists.playlists;
|
||||
if (playlistMap != null && !playlistMap.isEmpty()) {
|
||||
html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
|
||||
|
||||
for (PlayList[] innerLists : playlistMap.values()) {
|
||||
{
|
||||
if (innerLists != null && innerLists.length > 0) {
|
||||
PlayList playList = innerLists[0];
|
||||
if (playList != null && playList.playlistId != null && playList.title != null) {
|
||||
html.append("<tr><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.title)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(playList.playlistId)));
|
||||
html.append("</td></tr>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
html.append("</table>");
|
||||
} else {
|
||||
html.append(HtmlEscape.escapeHtml4(errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) {
|
||||
html.append("<h2>").append(HtmlEscape.escapeHtml4("Channel " + CHANNEL_BLUETOOTH_MAC)).append("</h2>");
|
||||
JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates();
|
||||
if (bluetoothStates == null) {
|
||||
return;
|
||||
}
|
||||
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<PairedDevice> pairedDeviceList = state.getPairedDeviceList();
|
||||
if (!pairedDeviceList.isEmpty()) {
|
||||
html.append("<table><tr><th align='left'>Name</th><th align='left'>Value</th></tr>");
|
||||
for (PairedDevice pairedDevice : pairedDeviceList) {
|
||||
html.append("<tr><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.friendlyName)));
|
||||
html.append("</td><td>");
|
||||
html.append(HtmlEscape.escapeHtml4(nullReplacement(pairedDevice.address)));
|
||||
html.append("</td></tr>");
|
||||
}
|
||||
html.append("</table>");
|
||||
} 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<String, String> headers = null;
|
||||
if (referer != null) {
|
||||
headers = new HashMap<>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (URISyntaxException | ConnectionException | InterruptedException e) {
|
||||
returnError(resp, e.getLocalizedMessage());
|
||||
return;
|
||||
}
|
||||
String response = connection.convertStream(urlConnection);
|
||||
returnHtml(connection, resp, response, site);
|
||||
}
|
||||
|
||||
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("<html>" + HtmlEscape.escapeHtml4(message) + "<br><a href='" + servletUrl
|
||||
+ "'>Try again</a></html>");
|
||||
} catch (IOException e) {
|
||||
logger.info("Returning error message failed", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,13 +12,20 @@
|
|||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
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.binding.amazonechocontrol.internal.smarthome.AlexaColor;
|
||||
import org.openhab.binding.amazonechocontrol.internal.util.ResourceUtil;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link AmazonEchoControlBindingConstants} class defines common constants, which are
|
||||
|
@ -37,30 +44,28 @@ 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");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_ECHO_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(THING_TYPE_ACCOUNT, THING_TYPE_ECHO, THING_TYPE_ECHO_SPOT, THING_TYPE_ECHO_SHOW,
|
||||
THING_TYPE_ECHO_WHA, THING_TYPE_FLASH_BRIEFING_PROFILE));
|
||||
public static final Set<ThingTypeUID> SUPPORTED_ECHO_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_ECHO,
|
||||
THING_TYPE_ECHO_SPOT, THING_TYPE_ECHO_SHOW, THING_TYPE_ECHO_WHA, THING_TYPE_FLASH_BRIEFING_PROFILE);
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_SMART_HOME_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(THING_TYPE_SMART_HOME_DEVICE, THING_TYPE_SMART_HOME_DEVICE_GROUP));
|
||||
public static final Set<ThingTypeUID> SUPPORTED_SMART_HOME_THING_TYPES_UIDS = Set.of(THING_TYPE_SMART_HOME_DEVICE,
|
||||
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";
|
||||
|
@ -69,11 +74,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";
|
||||
|
@ -84,37 +84,45 @@ public class AmazonEchoControlBindingConstants {
|
|||
public static final String CHANNEL_PLAY_MUSIC_VOICE_COMMAND = "playMusicVoiceCommand";
|
||||
public static final String CHANNEL_START_COMMAND = "startCommand";
|
||||
public static final String CHANNEL_LAST_VOICE_COMMAND = "lastVoiceCommand";
|
||||
public static final String CHANNEL_LAST_SPOKEN_TEXT = "lastSpokenText";
|
||||
public static final String CHANNEL_MEDIA_PROGRESS = "mediaProgress";
|
||||
public static final String CHANNEL_MEDIA_LENGTH = "mediaLength";
|
||||
public static final String CHANNEL_MEDIA_PROGRESS_TIME = "mediaProgressTime";
|
||||
public static final String CHANNEL_ASCENDING_ALARM = "ascendingAlarm";
|
||||
public static final String CHANNEL_DO_NOT_DISTURB = "doNotDisturb";
|
||||
public static final String CHANNEL_NOTIFICATION_VOLUME = "notificationVolume";
|
||||
public static final String CHANNEL_NEXT_REMINDER = "nextReminder";
|
||||
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";
|
||||
|
||||
// List of channel Type UIDs
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_BLUETHOOTH_MAC = new ChannelTypeUID(BINDING_ID, "bluetoothMAC");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_AMAZON_MUSIC_PLAY_LIST_ID = new ChannelTypeUID(BINDING_ID,
|
||||
"amazonMusicPlayListId");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_PLAY_ALARM_SOUND = new ChannelTypeUID(BINDING_ID, "playAlarmSound");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_CHANNEL_PLAY_ON_DEVICE = new ChannelTypeUID(BINDING_ID,
|
||||
"playOnDevice");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_MUSIC_PROVIDER_ID = new ChannelTypeUID(BINDING_ID,
|
||||
"musicProviderId");
|
||||
public static final ChannelTypeUID CHANNEL_TYPE_START_COMMAND = new ChannelTypeUID(BINDING_ID, "startCommand");
|
||||
|
||||
// List of all Properties
|
||||
public static final String DEVICE_PROPERTY_SERIAL_NUMBER = "serialNumber";
|
||||
public static final String DEVICE_PROPERTY_FAMILY = "deviceFamily";
|
||||
public static final String DEVICE_PROPERTY_DEVICE_TYPE_ID = "deviceTypeId";
|
||||
public static final String DEVICE_PROPERTY_MANUFACTURER_NAME = "manufacturerName";
|
||||
public static final String DEVICE_PROPERTY_DEVICE_IDENTIFIER_LIST = "deviceIdentifierList";
|
||||
public static final String DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE = "configurationJson";
|
||||
public static final String DEVICE_PROPERTY_ID = "id";
|
||||
|
||||
// Other
|
||||
public static final String FLASH_BRIEFING_COMMAND_PREFIX = "FlashBriefing.";
|
||||
|
||||
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<String, String> 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<AlexaColor> ALEXA_COLORS = ResourceUtil
|
||||
.readProperties(AlexaColor.class, "color.properties").entrySet().stream()
|
||||
.map(e -> new AlexaColor(e.getKey(), new HSBType(e.getValue()))).toList();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.openhab.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.binding.amazonechocontrol.internal.dto.DeviceTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.NotificationSoundTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.EchoHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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<FlashBriefingProfileHandler> flashBriefingProfileHandlers,
|
||||
Collection<DeviceTO> targets) {
|
||||
List<CommandOption> 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<EchoHandler> echoHandlers,
|
||||
Collection<FlashBriefingProfileHandler> flashBriefingProfileHandlers) {
|
||||
List<CommandOption> 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<NotificationSoundTO> alarmSounds) {
|
||||
List<CommandOption> 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));
|
||||
}
|
||||
}
|
|
@ -14,44 +14,35 @@ package org.openhab.binding.amazonechocontrol.internal;
|
|||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.discovery.AmazonEchoDiscovery;
|
||||
import org.openhab.binding.amazonechocontrol.internal.discovery.SmartHomeDevicesDiscovery;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.EchoHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.binding.amazonechocontrol.internal.util.NonNullListTypeAdapterFactory;
|
||||
import org.openhab.binding.amazonechocontrol.internal.util.SerializeNullTypeAdapterFactory;
|
||||
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;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
/**
|
||||
* The {@link AmazonEchoControlHandlerFactory} is responsible for creating things and thing
|
||||
|
@ -63,22 +54,42 @@ import com.google.gson.Gson;
|
|||
AmazonEchoControlHandlerFactory.class }, configurationPid = "binding.amazonechocontrol")
|
||||
@NonNullByDefault
|
||||
public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final Logger logger = LoggerFactory.getLogger(AmazonEchoControlHandlerFactory.class);
|
||||
private final Map<ThingUID, List<ServiceRegistration<?>>> discoveryServiceRegistrations = new HashMap<>();
|
||||
|
||||
private final Set<AccountHandler> 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 AmazonEchoControlStateDescriptionProvider amazonEchoControlStateDescriptionProvider;
|
||||
private final AmazonEchoControlCommandDescriptionProvider amazonEchoControlCommandDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public AmazonEchoControlHandlerFactory(@Reference HttpService httpService,
|
||||
@Reference StorageService storageService) {
|
||||
public AmazonEchoControlHandlerFactory(@Reference StorageService storageService,
|
||||
@Reference AmazonEchoControlStateDescriptionProvider dynamicStateDescriptionProvider,
|
||||
@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference AmazonEchoControlCommandDescriptionProvider amazonEchoControlCommandDescriptionProvider)
|
||||
throws Exception {
|
||||
this.storageService = storageService;
|
||||
this.httpService = httpService;
|
||||
this.gson = new Gson();
|
||||
this.bindingServlet = new BindingServlet(httpService);
|
||||
this.gson = new GsonBuilder().registerTypeAdapterFactory(new NonNullListTypeAdapterFactory())
|
||||
.registerTypeAdapterFactory(new SerializeNullTypeAdapterFactory()).create();
|
||||
this.amazonEchoControlStateDescriptionProvider = dynamicStateDescriptionProvider;
|
||||
this.amazonEchoControlCommandDescriptionProvider = amazonEchoControlCommandDescriptionProvider;
|
||||
|
||||
this.httpClient = httpClientFactory.createHttpClient("openhab-aec");
|
||||
this.http2Client = httpClientFactory.createHttp2Client("openhab-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();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,75 +98,33 @@ public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory {
|
|||
|| 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<String> storage = storageService.getStorage(thing.getUID().toString(),
|
||||
String.class.getClassLoader());
|
||||
AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage, gson);
|
||||
AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, storage, gson, httpClient, http2Client,
|
||||
amazonEchoControlCommandDescriptionProvider);
|
||||
accountHandlers.add(bridgeHandler);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
bindingServlet.addAccountThing(thing);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_FLASH_BRIEFING_PROFILE)) {
|
||||
Storage<String> storage = storageService.getStorage(thing.getUID().toString(),
|
||||
String.class.getClassLoader());
|
||||
return new FlashBriefingProfileHandler(thing, storage);
|
||||
Storage<? super Object> 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);
|
||||
return new EchoHandler(thing, gson, amazonEchoControlStateDescriptionProvider);
|
||||
} else if (SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new SmartHomeDeviceHandler(thing, gson);
|
||||
return new SmartHomeDeviceHandler(thing, gson, amazonEchoControlCommandDescriptionProvider,
|
||||
amazonEchoControlStateDescriptionProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(AccountHandler bridgeHandler) {
|
||||
List<ServiceRegistration<?>> discoveryServiceRegistration = Objects.requireNonNull(discoveryServiceRegistrations
|
||||
.computeIfAbsent(bridgeHandler.getThing().getUID(), k -> new ArrayList<>()));
|
||||
SmartHomeDevicesDiscovery smartHomeDevicesDiscovery = new SmartHomeDevicesDiscovery(bridgeHandler);
|
||||
smartHomeDevicesDiscovery.activate();
|
||||
discoveryServiceRegistration.add(bundleContext.registerService(DiscoveryService.class.getName(),
|
||||
smartHomeDevicesDiscovery, new Hashtable<>()));
|
||||
|
||||
AmazonEchoDiscovery discoveryService = new AmazonEchoDiscovery(bridgeHandler);
|
||||
discoveryService.activate();
|
||||
discoveryServiceRegistration.add(
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
List<ServiceRegistration<?>> discoveryServiceRegistration = discoveryServiceRegistrations
|
||||
.remove(thingHandler.getThing().getUID());
|
||||
if (discoveryServiceRegistration != null) {
|
||||
discoveryServiceRegistration.forEach(serviceReg -> {
|
||||
AbstractDiscoveryService service = (AbstractDiscoveryService) bundleContext
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (service != null) {
|
||||
if (service instanceof AmazonEchoDiscovery discovery) {
|
||||
discovery.deactivate();
|
||||
} else if (service instanceof SmartHomeDevicesDiscovery discovery) {
|
||||
discovery.deactivate();
|
||||
} else {
|
||||
logger.warn("Found unknown discovery-service instance: {}", service);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,516 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.eclipse.jetty.util.StringUtil.isNotBlank;
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlServlet.SERVLET_PATH;
|
||||
import static org.openhab.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.binding.amazonechocontrol.internal.connection.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.DeviceTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.NotificationSoundTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.response.BluetoothStateTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.response.MusicProviderTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.util.HttpRequestBuilder;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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<String, String[]> 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<String, List<ChannelOption>> channels = new HashMap<>();
|
||||
List<ChannelOption> 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<ChannelOption> 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<BluetoothStateTO> states = connection.getBluetoothConnectionStates();
|
||||
List<ChannelOption> 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<String, String> 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("<html>" + escapeHtml4(message) + "<br><a href='" + tryAgainUri + "'>Try again</a></html>");
|
||||
}
|
||||
|
||||
private Map<String, String> getQueryMap(@Nullable String query) {
|
||||
Map<String, String> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Dynamic channel state description provider.
|
||||
* Overrides the state description for the controls, which receive its configuration in the runtime.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, AmazonEchoControlStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class AmazonEchoControlStateDescriptionProvider implements DynamicStateDescriptionProvider {
|
||||
private final Map<ChannelUID, StateDescription> descriptions = new ConcurrentHashMap<>();
|
||||
private final Logger logger = LoggerFactory.getLogger(AmazonEchoControlStateDescriptionProvider.class);
|
||||
|
||||
/**
|
||||
* Set a state description for a channel. This description will be used when preparing the channel state by
|
||||
* the framework for presentation. A previous description, if existed, will be replaced.
|
||||
*
|
||||
* @param channelUID channel UID
|
||||
* @param description state description for the channel
|
||||
*/
|
||||
public void setDescription(ChannelUID channelUID, StateDescription description) {
|
||||
logger.trace("adding state description for channel {}", channelUID);
|
||||
descriptions.put(channelUID, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* remove all descriptions for a given thing
|
||||
*
|
||||
* @param thingUID the thing's UID
|
||||
*/
|
||||
public void removeDescriptionsForThing(ThingUID thingUID) {
|
||||
logger.trace("removing state description for thing {}", thingUID);
|
||||
descriptions.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||
if (descriptions.containsKey(channel.getUID())) {
|
||||
logger.trace("returning new stateDescription for {}", channel.getUID());
|
||||
return descriptions.get(channel.getUID());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.EchoHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.BluetoothState;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonBluetoothStates.PairedDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists.PlayList;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic channel state description provider.
|
||||
* Overrides the state description for the controls, which receive its configuration in the runtime.
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, AmazonEchoDynamicStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class AmazonEchoDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider {
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public AmazonEchoDynamicStateDescriptionProvider(@Reference ThingRegistry thingRegistry) {
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
public @Nullable ThingHandler findHandler(Channel channel) {
|
||||
Thing thing = thingRegistry.get(channel.getUID().getThingUID());
|
||||
if (thing == null) {
|
||||
return null;
|
||||
}
|
||||
ThingUID accountThingId = thing.getBridgeUID();
|
||||
if (accountThingId == null) {
|
||||
return null;
|
||||
}
|
||||
Thing accountThing = thingRegistry.get(accountThingId);
|
||||
if (accountThing == null) {
|
||||
return null;
|
||||
}
|
||||
AccountHandler accountHandler = (AccountHandler) accountThing.getHandler();
|
||||
if (accountHandler == null) {
|
||||
return null;
|
||||
}
|
||||
Connection connection = accountHandler.findConnection();
|
||||
if (connection == null || !connection.getIsLoggedIn()) {
|
||||
return null;
|
||||
}
|
||||
return thing.getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StateDescription getStateDescription(Channel channel,
|
||||
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
|
||||
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
|
||||
if (channelTypeUID == null || !BINDING_ID.equals(channelTypeUID.getBindingId())) {
|
||||
return null;
|
||||
}
|
||||
if (originalStateDescription == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (CHANNEL_TYPE_BLUETHOOTH_MAC.equals(channel.getChannelTypeUID())) {
|
||||
EchoHandler handler = (EchoHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
BluetoothState bluetoothState = handler.findBluetoothState();
|
||||
if (bluetoothState == null) {
|
||||
return null;
|
||||
}
|
||||
List<PairedDevice> pairedDeviceList = bluetoothState.getPairedDeviceList();
|
||||
if (pairedDeviceList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
options.add(new StateOption("", ""));
|
||||
for (PairedDevice device : pairedDeviceList) {
|
||||
final String value = device.address;
|
||||
if (value != null && device.friendlyName != null) {
|
||||
options.add(new StateOption(value, device.friendlyName));
|
||||
}
|
||||
}
|
||||
StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription)
|
||||
.withOptions(options).build().toStateDescription();
|
||||
return result;
|
||||
} else if (CHANNEL_TYPE_AMAZON_MUSIC_PLAY_LIST_ID.equals(channel.getChannelTypeUID())) {
|
||||
EchoHandler handler = (EchoHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JsonPlaylists playLists = handler.findPlaylists();
|
||||
if (playLists == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
options.add(new StateOption("", ""));
|
||||
Map<String, PlayList @Nullable []> playlistMap = playLists.playlists;
|
||||
if (playlistMap != null) {
|
||||
for (PlayList[] innerLists : playlistMap.values()) {
|
||||
if (innerLists != null && innerLists.length > 0) {
|
||||
PlayList playList = innerLists[0];
|
||||
final String value = playList.playlistId;
|
||||
if (value != null && playList.title != null) {
|
||||
options.add(new StateOption(value,
|
||||
String.format("%s (%d)", playList.title, playList.trackCount)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription)
|
||||
.withOptions(options).build().toStateDescription();
|
||||
return result;
|
||||
} else if (CHANNEL_TYPE_PLAY_ALARM_SOUND.equals(channel.getChannelTypeUID())) {
|
||||
EchoHandler handler = (EchoHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<JsonNotificationSound> notificationSounds = handler.findAlarmSounds();
|
||||
if (notificationSounds.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
options.add(new StateOption("", ""));
|
||||
|
||||
for (JsonNotificationSound notificationSound : notificationSounds) {
|
||||
if (notificationSound.folder == null && notificationSound.providerId != null
|
||||
&& notificationSound.id != null && notificationSound.displayName != null) {
|
||||
String providerSoundId = notificationSound.providerId + ":" + notificationSound.id;
|
||||
options.add(new StateOption(providerSoundId, notificationSound.displayName));
|
||||
}
|
||||
}
|
||||
StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription)
|
||||
.withOptions(options).build().toStateDescription();
|
||||
return result;
|
||||
} else if (CHANNEL_TYPE_CHANNEL_PLAY_ON_DEVICE.equals(channel.getChannelTypeUID())) {
|
||||
FlashBriefingProfileHandler handler = (FlashBriefingProfileHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
AccountHandler accountHandler = handler.findAccountHandler();
|
||||
if (accountHandler == null) {
|
||||
return null;
|
||||
}
|
||||
List<Device> devices = accountHandler.getLastKnownDevices();
|
||||
if (devices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
options.add(new StateOption("", ""));
|
||||
for (Device device : devices) {
|
||||
final String value = device.serialNumber;
|
||||
if (value != null && device.getCapabilities().contains("FLASH_BRIEFING")) {
|
||||
options.add(new StateOption(value, device.accountName));
|
||||
}
|
||||
}
|
||||
return StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(options).build()
|
||||
.toStateDescription();
|
||||
} else if (CHANNEL_TYPE_MUSIC_PROVIDER_ID.equals(channel.getChannelTypeUID())) {
|
||||
EchoHandler handler = (EchoHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
List<JsonMusicProvider> musicProviders = handler.findMusicProviders();
|
||||
if (musicProviders.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
for (JsonMusicProvider musicProvider : musicProviders) {
|
||||
List<String> 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()) {
|
||||
options.add(new StateOption(providerId, displayName));
|
||||
}
|
||||
}
|
||||
return StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(options).build()
|
||||
.toStateDescription();
|
||||
} else if (CHANNEL_TYPE_START_COMMAND.equals(channel.getChannelTypeUID())) {
|
||||
EchoHandler handler = (EchoHandler) findHandler(channel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
AccountHandler account = handler.findAccount();
|
||||
if (account == null) {
|
||||
return null;
|
||||
}
|
||||
List<FlashBriefingProfileHandler> flashbriefings = account.getFlashBriefingProfileHandlers();
|
||||
if (flashbriefings.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
options.addAll(originalStateDescription.getOptions());
|
||||
|
||||
for (FlashBriefingProfileHandler flashBriefing : flashbriefings) {
|
||||
String value = FLASH_BRIEFING_COMMAND_PREFIX + flashBriefing.getThing().getUID().getId();
|
||||
String displayName = flashBriefing.getThing().getLabel();
|
||||
options.add(new StateOption(value, displayName));
|
||||
}
|
||||
return StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(options).build()
|
||||
.toStateDescription();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_NAME;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
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);
|
||||
|
||||
String servletUrlWithoutRoot;
|
||||
String servletUrl;
|
||||
HttpService httpService;
|
||||
|
||||
List<Thing> accountHandlers = new ArrayList<>();
|
||||
|
||||
public BindingServlet(HttpService httpService) {
|
||||
this.httpService = httpService;
|
||||
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("<html><head><title>" + HtmlEscape.escapeHtml4(BINDING_NAME) + "</title><head><body>");
|
||||
html.append("<h1>" + HtmlEscape.escapeHtml4(BINDING_NAME) + "</h1>");
|
||||
|
||||
synchronized (accountHandlers) {
|
||||
if (accountHandlers.isEmpty()) {
|
||||
html.append("No Account thing created.");
|
||||
} else {
|
||||
for (Thing accountHandler : accountHandlers) {
|
||||
String url = URLEncoder.encode(accountHandler.getUID().getId(), "UTF8");
|
||||
html.append("<a href='./" + url + " '>" + HtmlEscape.escapeHtml4(accountHandler.getLabel())
|
||||
+ "</a><br>");
|
||||
}
|
||||
}
|
||||
}
|
||||
html.append("</body></html>");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@
|
|||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ConnectionException} is used for errors in the connection to the amazon server
|
||||
|
@ -20,11 +21,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConnectionException extends RuntimeException {
|
||||
public class ConnectionException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConnectionException(@Nullable String message, @Nullable Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_ID;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
@ -40,8 +42,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;
|
||||
}
|
||||
|
||||
|
@ -83,7 +84,7 @@ public class ConsoleCommandExtension extends AbstractConsoleCommandExtension {
|
|||
.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.");
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HttpException} is used for http error codes
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HttpException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
int code;
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public HttpException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPushCommand;
|
||||
|
||||
/**
|
||||
* The {@link IWebSocketCommandHandler} is used for the web socket handler implementation
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IWebSocketCommandHandler {
|
||||
|
||||
void webSocketCommandReceived(JsonPushCommand pushCommand);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import static org.eclipse.jetty.util.StringUtil.isNotBlank;
|
||||
import static org.openhab.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) : "");
|
||||
}
|
||||
}
|
|
@ -1,608 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
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.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPushCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* 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 Gson gson = new Gson();
|
||||
private final WebSocketClient webSocketClient;
|
||||
private final IWebSocketCommandHandler webSocketCommandHandler;
|
||||
private final AmazonEchoControlWebSocket amazonEchoControlWebSocket;
|
||||
|
||||
private @Nullable Session session;
|
||||
private @Nullable Timer pingTimer;
|
||||
private @Nullable Timer pongTimeoutTimer;
|
||||
private @Nullable Future<?> sessionFuture;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
public WebSocketConnection(String amazonSite, List<HttpCookie> sessionCookies,
|
||||
IWebSocketCommandHandler webSocketCommandHandler) throws IOException {
|
||||
this.webSocketCommandHandler = webSocketCommandHandler;
|
||||
amazonEchoControlWebSocket = new AmazonEchoControlWebSocket();
|
||||
HttpClient httpClient = new HttpClient(new SslContextFactory.Client());
|
||||
webSocketClient = new WebSocketClient(httpClient);
|
||||
try {
|
||||
String host;
|
||||
if ("amazon.com".equalsIgnoreCase(amazonSite)) {
|
||||
host = "dp-gw-na-js." + amazonSite;
|
||||
} else {
|
||||
host = "dp-gw-na." + amazonSite;
|
||||
}
|
||||
|
||||
String deviceSerial = "";
|
||||
List<HttpCookie> cookiesForWs = new ArrayList<>();
|
||||
for (HttpCookie cookie : sessionCookies) {
|
||||
if (cookie.getName().equals("ubid-acbde")) {
|
||||
deviceSerial = cookie.getValue();
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
deviceSerial += "-" + new Date().getTime();
|
||||
URI uri;
|
||||
|
||||
uri = new URI("wss://" + host + "/?x-amz-device-type=ALEGCNGL9K0HM&x-amz-device-serial=" + deviceSerial);
|
||||
|
||||
try {
|
||||
webSocketClient.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Web socket start failed", e);
|
||||
throw new IOException("Web socket start failed");
|
||||
}
|
||||
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setHeader("Host", host);
|
||||
request.setHeader("Origin", "alexa." + amazonSite);
|
||||
request.setCookies(cookiesForWs);
|
||||
|
||||
initPongTimeoutTimer();
|
||||
|
||||
sessionFuture = webSocketClient.connect(amazonEchoControlWebSocket, uri, request);
|
||||
} catch (URISyntaxException e) {
|
||||
logger.debug("Initialize web socket failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setSession(Session session) {
|
||||
this.session = session;
|
||||
logger.debug("Web Socket session started");
|
||||
Timer pingTimer = new Timer();
|
||||
this.pingTimer = pingTimer;
|
||||
pingTimer.schedule(new TimerTask() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
amazonEchoControlWebSocket.sendPing();
|
||||
}
|
||||
}, 180000, 180000);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
closed = true;
|
||||
Timer pingTimer = this.pingTimer;
|
||||
if (pingTimer != null) {
|
||||
pingTimer.cancel();
|
||||
}
|
||||
clearPongTimeoutTimer();
|
||||
Session session = this.session;
|
||||
this.session = null;
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close();
|
||||
} catch (Exception e) {
|
||||
logger.debug("Closing session failed", e);
|
||||
}
|
||||
}
|
||||
logger.trace("Connect future = {}", sessionFuture);
|
||||
final Future<?> sessionFuture = this.sessionFuture;
|
||||
if (sessionFuture != null && !sessionFuture.isDone()) {
|
||||
sessionFuture.cancel(true);
|
||||
}
|
||||
try {
|
||||
webSocketClient.stop();
|
||||
} catch (InterruptedException e) {
|
||||
// Just ignore
|
||||
} catch (Exception e) {
|
||||
logger.debug("Stopping websocket failed", e);
|
||||
}
|
||||
webSocketClient.destroy();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@WebSocket(maxTextMessageSize = 64 * 1024, maxBinaryMessageSize = 64 * 1024)
|
||||
public class AmazonEchoControlWebSocket {
|
||||
int msgCounter = -1;
|
||||
int messageId;
|
||||
|
||||
AmazonEchoControlWebSocket() {
|
||||
this.messageId = ThreadLocalRandom.current().nextInt(0, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
void sendMessage(String message) {
|
||||
sendMessage(message.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
void sendMessageHex(String message) {
|
||||
sendMessage(hexStringToByteArray(message));
|
||||
}
|
||||
|
||||
void sendMessage(byte[] buffer) {
|
||||
try {
|
||||
logger.debug("Send message with length {}", buffer.length);
|
||||
Session session = WebSocketConnection.this.session;
|
||||
if (session != null) {
|
||||
session.getRemote().sendBytes(ByteBuffer.wrap(buffer));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("Send message failed", e);
|
||||
WebSocketConnection.this.close();
|
||||
}
|
||||
}
|
||||
|
||||
byte[] hexStringToByteArray(String str) {
|
||||
byte[] bytes = new byte[str.length() / 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
String strValue = str.substring(2 * i, 2 * i + 2);
|
||||
bytes[i] = (byte) Integer.parseInt(strValue, 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
long readHex(byte[] data, int index, int length) {
|
||||
String str = readString(data, index, length);
|
||||
if (str.startsWith("0x")) {
|
||||
str = str.substring(2);
|
||||
}
|
||||
return Long.parseLong(str, 16);
|
||||
}
|
||||
|
||||
String readString(byte[] data, int index, int length) {
|
||||
return new String(data, index, length, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
class Message {
|
||||
String service = "";
|
||||
Content content = new Content();
|
||||
String contentTune = "";
|
||||
String messageType = "";
|
||||
long channel;
|
||||
long checksum;
|
||||
long messageId;
|
||||
String moreFlag = "";
|
||||
long seq;
|
||||
}
|
||||
|
||||
class Content {
|
||||
String messageType = "";
|
||||
String protocolVersion = "";
|
||||
String connectionUUID = "";
|
||||
long established;
|
||||
long timestampINI;
|
||||
long timestampACK;
|
||||
String subMessageType = "";
|
||||
long channel;
|
||||
String destinationIdentityUrn = "";
|
||||
String deviceIdentityUrn = "";
|
||||
@Nullable
|
||||
String payload;
|
||||
byte[] payloadData = new byte[0];
|
||||
@Nullable
|
||||
JsonPushCommand pushCommand;
|
||||
}
|
||||
|
||||
Message parseIncomingMessage(byte[] data) {
|
||||
int idx = 0;
|
||||
Message message = new Message();
|
||||
message.service = readString(data, data.length - 4, 4);
|
||||
|
||||
if (message.service.equals("TUNE")) {
|
||||
message.checksum = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
int contentLength = (int) readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.contentTune = readString(data, idx, contentLength - 4 - idx);
|
||||
} else if (message.service.equals("FABE")) {
|
||||
message.messageType = readString(data, idx, 3);
|
||||
idx += 4;
|
||||
message.channel = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.messageId = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.moreFlag = readString(data, idx, 1);
|
||||
idx += 2; // 1 + delimiter;
|
||||
message.seq = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.checksum = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
|
||||
// currently not used: long contentLength = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
|
||||
message.content.messageType = readString(data, idx, 3);
|
||||
idx += 4;
|
||||
|
||||
if (message.channel == 0x361) { // GW_HANDSHAKE_CHANNEL
|
||||
if (message.content.messageType.equals("ACK")) {
|
||||
int length = (int) readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.content.protocolVersion = readString(data, idx, length);
|
||||
idx += length + 1;
|
||||
length = (int) readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.content.connectionUUID = readString(data, idx, length);
|
||||
idx += length + 1;
|
||||
message.content.established = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.content.timestampINI = readHex(data, idx, 18);
|
||||
idx += 19; // 18 + delimiter;
|
||||
message.content.timestampACK = readHex(data, idx, 18);
|
||||
idx += 19; // 18 + delimiter;
|
||||
}
|
||||
} else if (message.channel == 0x362) { // GW_CHANNEL
|
||||
if (message.content.messageType.equals("GWM")) {
|
||||
message.content.subMessageType = readString(data, idx, 3);
|
||||
idx += 4;
|
||||
message.content.channel = readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
|
||||
if (message.content.channel == 0xb479) { // DEE_WEBSITE_MESSAGING
|
||||
int length = (int) readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
message.content.destinationIdentityUrn = readString(data, idx, length);
|
||||
idx += length + 1;
|
||||
|
||||
length = (int) readHex(data, idx, 10);
|
||||
idx += 11; // 10 + delimiter;
|
||||
String idData = readString(data, idx, length);
|
||||
idx += length + 1;
|
||||
|
||||
String[] idDataElements = idData.split(" ", 2);
|
||||
message.content.deviceIdentityUrn = idDataElements[0];
|
||||
String payload = null;
|
||||
if (idDataElements.length == 2) {
|
||||
payload = idDataElements[1];
|
||||
}
|
||||
if (payload == null) {
|
||||
payload = readString(data, idx, data.length - 4 - idx);
|
||||
}
|
||||
if (!payload.isEmpty()) {
|
||||
try {
|
||||
message.content.pushCommand = gson.fromJson(payload, JsonPushCommand.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.info("Parsing json failed, illegal JSON: {}", payload, e);
|
||||
}
|
||||
}
|
||||
message.content.payload = payload;
|
||||
}
|
||||
}
|
||||
} else if (message.channel == 0x65) { // CHANNEL_FOR_HEARTBEAT
|
||||
idx -= 1; // no delimiter!
|
||||
message.content.payloadData = Arrays.copyOfRange(data, idx, data.length - 4);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onWebSocketConnect(@Nullable Session session) {
|
||||
if (session != null) {
|
||||
this.msgCounter = -1;
|
||||
setSession(session);
|
||||
sendMessage("0x99d4f71a 0x0000001d A:HTUNE");
|
||||
} else {
|
||||
logger.debug("Web Socket connect without session");
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onWebSocketBinary(byte @Nullable [] data, int offset, int len) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.msgCounter++;
|
||||
if (this.msgCounter == 0) {
|
||||
sendMessage(
|
||||
"0xa6f6a951 0x0000009c {\"protocolName\":\"A:H\",\"parameters\":{\"AlphaProtocolHandler.receiveWindowSize\":\"16\",\"AlphaProtocolHandler.maxFragmentSize\":\"16000\"}}TUNE");
|
||||
sendMessage(encodeGWHandshake());
|
||||
} else if (this.msgCounter == 1) {
|
||||
sendMessage(encodeGWRegister());
|
||||
sendPing();
|
||||
} else {
|
||||
byte[] buffer = data;
|
||||
if (offset > 0 || len != buffer.length) {
|
||||
buffer = Arrays.copyOfRange(data, offset, offset + len);
|
||||
}
|
||||
try {
|
||||
Message message = parseIncomingMessage(buffer);
|
||||
if (message.service.equals("FABE") && message.content.messageType.equals("PON")
|
||||
&& message.content.payloadData.length > 0) {
|
||||
logger.debug("Pong received");
|
||||
WebSocketConnection.this.clearPongTimeoutTimer();
|
||||
return;
|
||||
} else {
|
||||
JsonPushCommand pushCommand = message.content.pushCommand;
|
||||
logger.debug("Message received: {}", message.content.payload);
|
||||
if (pushCommand != null) {
|
||||
webSocketCommandHandler.webSocketCommandReceived(pushCommand);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Handling of push notification failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onWebSocketText(@Nullable String message) {
|
||||
logger.trace("Received text message: '{}'", message);
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onWebSocketClose(int code, @Nullable String reason) {
|
||||
logger.info("Web Socket close {}. Reason: {}", code, reason);
|
||||
WebSocketConnection.this.close();
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onWebSocketError(@Nullable Throwable error) {
|
||||
logger.info("Web Socket error", error);
|
||||
if (!closed) {
|
||||
WebSocketConnection.this.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPing() {
|
||||
logger.debug("Send Ping");
|
||||
WebSocketConnection.this.initPongTimeoutTimer();
|
||||
sendMessage(encodePing());
|
||||
}
|
||||
|
||||
String encodeNumber(long val) {
|
||||
return encodeNumber(val, 8);
|
||||
}
|
||||
|
||||
String encodeNumber(long val, int len) {
|
||||
String str = Long.toHexString(val);
|
||||
if (str.length() > len) {
|
||||
str = str.substring(str.length() - len);
|
||||
}
|
||||
while (str.length() < len) {
|
||||
str = '0' + str;
|
||||
}
|
||||
return "0x" + str;
|
||||
}
|
||||
|
||||
long computeBits(long input, long len) {
|
||||
long lenCounter = len;
|
||||
long value;
|
||||
for (value = toUnsignedInt(input); 0 != lenCounter && 0 != value;) {
|
||||
value = (long) Math.floor(value / 2);
|
||||
lenCounter--;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
long toUnsignedInt(long value) {
|
||||
long result = value;
|
||||
if (0 > value) {
|
||||
result = 4294967295L + value + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int computeChecksum(byte[] data, int exclusionStart, int exclusionEnd) {
|
||||
if (exclusionEnd < exclusionStart) {
|
||||
return 0;
|
||||
}
|
||||
long overflow;
|
||||
long sum;
|
||||
int index;
|
||||
for (overflow = 0, sum = 0, index = 0; index < data.length; index++) {
|
||||
if (index != exclusionStart) {
|
||||
sum += toUnsignedInt((data[index] & 0xFF) << ((index & 3 ^ 3) << 3));
|
||||
overflow += computeBits(sum, 32);
|
||||
sum = toUnsignedInt((int) sum & (int) 4294967295L);
|
||||
} else {
|
||||
index = exclusionEnd - 1;
|
||||
}
|
||||
}
|
||||
while (overflow != 0) {
|
||||
sum += overflow;
|
||||
overflow = computeBits(sum, 32);
|
||||
sum = (int) sum & (int) 4294967295L;
|
||||
}
|
||||
long value = toUnsignedInt(sum);
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
byte[] encodeGWHandshake() {
|
||||
// pubrelBuf = new Buffer('MSG 0x00000361 0x0e414e45 f 0x00000001 0xd7c62f29 0x0000009b INI 0x00000003 1.0
|
||||
// 0x00000024 ff1c4525-c036-4942-bf6c-a098755ac82f 0x00000164d106ce6b END FABE');
|
||||
this.messageId++;
|
||||
String msg = "MSG 0x00000361 "; // Message-type and Channel = GW_HANDSHAKE_CHANNEL;
|
||||
msg += this.encodeNumber(this.messageId) + " f 0x00000001 ";
|
||||
int checkSumStart = msg.length();
|
||||
msg += "0x00000000 "; // Checksum!
|
||||
int checkSumEnd = msg.length();
|
||||
msg += "0x0000009b "; // length content
|
||||
msg += "INI 0x00000003 1.0 0x00000024 "; // content part 1
|
||||
msg += UUID.randomUUID().toString();
|
||||
msg += ' ';
|
||||
msg += this.encodeNumber(new Date().getTime(), 16);
|
||||
msg += " END FABE";
|
||||
// msg = "MSG 0x00000361 0x0e414e45 f 0x00000001 0xd7c62f29 0x0000009b INI 0x00000003 1.0 0x00000024
|
||||
// ff1c4525-c036-4942-bf6c-a098755ac82f 0x00000164d106ce6b END FABE";
|
||||
byte[] completeBuffer = msg.getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
int checksum = this.computeChecksum(completeBuffer, checkSumStart, checkSumEnd);
|
||||
String checksumHex = encodeNumber(checksum);
|
||||
byte[] checksumBuf = checksumHex.getBytes(StandardCharsets.US_ASCII);
|
||||
System.arraycopy(checksumBuf, 0, completeBuffer, checkSumStart, checksumBuf.length);
|
||||
|
||||
return completeBuffer;
|
||||
}
|
||||
|
||||
byte[] encodeGWRegister() {
|
||||
// pubrelBuf = new Buffer('MSG 0x00000362 0x0e414e46 f 0x00000001 0xf904b9f5 0x00000109 GWM MSG 0x0000b479
|
||||
// 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041
|
||||
// urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService
|
||||
// {"command":"REGISTER_CONNECTION"}FABE');
|
||||
this.messageId++;
|
||||
String msg = "MSG 0x00000362 "; // Message-type and Channel = GW_CHANNEL;
|
||||
msg += this.encodeNumber(this.messageId) + " f 0x00000001 ";
|
||||
int checkSumStart = msg.length();
|
||||
msg += "0x00000000 "; // Checksum!
|
||||
int checkSumEnd = msg.length();
|
||||
msg += "0x00000109 "; // length content
|
||||
msg += "GWM MSG 0x0000b479 0x0000003b urn:tcomm-endpoint:device:deviceType:0:deviceSerialNumber:0 0x00000041 urn:tcomm-endpoint:service:serviceName:DeeWebsiteMessagingService {\"command\":\"REGISTER_CONNECTION\"}FABE";
|
||||
|
||||
byte[] completeBuffer = msg.getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
int checksum = this.computeChecksum(completeBuffer, checkSumStart, checkSumEnd);
|
||||
|
||||
String checksumHex = encodeNumber(checksum);
|
||||
byte[] checksumBuf = checksumHex.getBytes(StandardCharsets.US_ASCII);
|
||||
System.arraycopy(checksumBuf, 0, completeBuffer, checkSumStart, checksumBuf.length);
|
||||
|
||||
String test = readString(completeBuffer, 0, completeBuffer.length);
|
||||
test.toString();
|
||||
return completeBuffer;
|
||||
}
|
||||
|
||||
void encode(byte[] data, long b, int offset, int len) {
|
||||
for (int index = 0; index < len; index++) {
|
||||
data[index + offset] = (byte) (b >> 8 * (len - 1 - index) & 255);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] encodePing() {
|
||||
// MSG 0x00000065 0x0e414e47 f 0x00000001 0xbc2fbb5f 0x00000062
|
||||
this.messageId++;
|
||||
String msg = "MSG 0x00000065 "; // Message-type and Channel = CHANNEL_FOR_HEARTBEAT;
|
||||
msg += this.encodeNumber(this.messageId) + " f 0x00000001 ";
|
||||
int checkSumStart = msg.length();
|
||||
msg += "0x00000000 "; // Checksum!
|
||||
int checkSumEnd = msg.length();
|
||||
msg += "0x00000062 "; // length content
|
||||
|
||||
byte[] completeBuffer = new byte[0x62];
|
||||
byte[] startBuffer = msg.getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
System.arraycopy(startBuffer, 0, completeBuffer, 0, startBuffer.length);
|
||||
|
||||
byte[] header = "PIN".getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] payload = "Regular".getBytes(StandardCharsets.US_ASCII); // g = h.length
|
||||
byte[] bufferPing = new byte[header.length + 4 + 8 + 4 + 2 * payload.length];
|
||||
int idx = 0;
|
||||
System.arraycopy(header, 0, bufferPing, 0, header.length);
|
||||
idx += header.length;
|
||||
encode(bufferPing, 0, idx, 4);
|
||||
idx += 4;
|
||||
encode(bufferPing, new Date().getTime(), idx, 8);
|
||||
idx += 8;
|
||||
encode(bufferPing, payload.length, idx, 4);
|
||||
idx += 4;
|
||||
|
||||
for (int q = 0; q < payload.length; q++) {
|
||||
bufferPing[idx + q * 2] = (byte) 0;
|
||||
bufferPing[idx + q * 2 + 1] = payload[q];
|
||||
}
|
||||
System.arraycopy(bufferPing, 0, completeBuffer, startBuffer.length, bufferPing.length);
|
||||
|
||||
byte[] buf2End = "FABE".getBytes(StandardCharsets.US_ASCII);
|
||||
System.arraycopy(buf2End, 0, completeBuffer, startBuffer.length + bufferPing.length, buf2End.length);
|
||||
|
||||
int checksum = this.computeChecksum(completeBuffer, checkSumStart, checkSumEnd);
|
||||
String checksumHex = encodeNumber(checksum);
|
||||
byte[] checksumBuf = checksumHex.getBytes(StandardCharsets.US_ASCII);
|
||||
System.arraycopy(checksumBuf, 0, completeBuffer, checkSumStart, checksumBuf.length);
|
||||
return completeBuffer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.channelhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link ChannelHandler} is the base class for all channel handlers
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class ChannelHandler {
|
||||
|
||||
public abstract boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command)
|
||||
throws IOException, URISyntaxException, InterruptedException;
|
||||
|
||||
protected final IAmazonThingHandler thingHandler;
|
||||
protected final Gson gson;
|
||||
private final Logger logger;
|
||||
|
||||
protected ChannelHandler(IAmazonThingHandler thingHandler, Gson gson) {
|
||||
this.logger = LoggerFactory.getLogger(this.getClass());
|
||||
this.thingHandler = thingHandler;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
protected <T> @Nullable T tryParseJson(String json, Class<T> type) {
|
||||
try {
|
||||
return gson.fromJson(json, type);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.debug("Json parse error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> @Nullable T parseJson(String json, Class<T> type) throws JsonSyntaxException {
|
||||
return gson.fromJson(json, type);
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.channelhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.unbescape.xml.XmlEscape;
|
||||
|
||||
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";
|
||||
|
||||
protected final IEchoThingHandler thingHandler;
|
||||
|
||||
public ChannelHandlerAnnouncement(IEchoThingHandler thingHandler, Gson gson) {
|
||||
super(thingHandler, gson);
|
||||
this.thingHandler = thingHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command)
|
||||
throws IOException, URISyntaxException {
|
||||
if (channelId.equals(CHANNEL_NAME)) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
String commandValue = stringCommand.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 = "<speak>" + XmlEscape.escapeXml10(speak) + "</speak>";
|
||||
}
|
||||
if (sound && speak.startsWith("<speak>")) {
|
||||
body = "Error: The combination of sound and speak in SSML syntax is not allowed";
|
||||
title = "Error";
|
||||
speak = "<speak><lang xml:lang=\"en-UK\">Error: The combination of sound and speak in <prosody rate=\"x-slow\"><say-as interpret-as=\"characters\">SSML</say-as></prosody> syntax is not allowed</lang></speak>";
|
||||
}
|
||||
}
|
||||
if ("<speak> </speak>".equals(speak)) {
|
||||
volume = -1; // Do not change volume
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
body = "Invalid Json." + e.getLocalizedMessage();
|
||||
title = "Error";
|
||||
speak = "<speak><lang xml:lang=\"en-US\">" + XmlEscape.escapeXml10(body) + "</lang></speak>";
|
||||
body = e.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
thingHandler.startAnnouncement(device, speak, Objects.requireNonNullElse(body, ""), title, volume);
|
||||
}
|
||||
refreshChannel();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refreshChannel() {
|
||||
thingHandler.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;
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.channelhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
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(IAmazonThingHandler thingHandler, Gson gson) {
|
||||
super(thingHandler, gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryHandleCommand(Device device, Connection connection, String channelId, Command command)
|
||||
throws IOException, URISyntaxException, InterruptedException {
|
||||
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, null);
|
||||
}
|
||||
refreshChannel();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refreshChannel() {
|
||||
thingHandler.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.channelhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link IAmazonThingHandler} is used from ChannelHandlers to communicate
|
||||
* with the thing
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IAmazonThingHandler {
|
||||
|
||||
void updateChannelState(String channelId, State state);
|
||||
|
||||
void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title,
|
||||
@Nullable Integer volume) throws IOException, URISyntaxException;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.channelhandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
|
||||
/**
|
||||
* The {@link IEchoThingHandler} is used from ChannelHandlers to communicate with the thing
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IEchoThingHandler extends IAmazonThingHandler {
|
||||
@Override
|
||||
void startAnnouncement(Device device, String speak, String bodyText, @Nullable String title,
|
||||
@Nullable Integer volume) throws IOException, URISyntaxException;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.connection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.DeviceTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.request.AnnouncementContentTO;
|
||||
|
||||
/**
|
||||
* The {@link AnnouncementWrapper} is a wrapper for announcement instructions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AnnouncementWrapper {
|
||||
private final List<DeviceTO> devices = new ArrayList<>();
|
||||
private final List<@Nullable Integer> ttsVolumes = new ArrayList<>();
|
||||
private final List<@Nullable Integer> standardVolumes = new ArrayList<>();
|
||||
|
||||
private final String speak;
|
||||
private final String bodyText;
|
||||
private final @Nullable String title;
|
||||
|
||||
public AnnouncementWrapper(String speak, String bodyText, @Nullable String title) {
|
||||
this.speak = speak;
|
||||
this.bodyText = bodyText;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public void add(DeviceTO device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) {
|
||||
devices.add(device);
|
||||
ttsVolumes.add(ttsVolume);
|
||||
standardVolumes.add(standardVolume);
|
||||
}
|
||||
|
||||
public List<DeviceTO> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
public String getSpeak() {
|
||||
return speak;
|
||||
}
|
||||
|
||||
public String getBodyText() {
|
||||
return bodyText;
|
||||
}
|
||||
|
||||
public @Nullable String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public List<@Nullable Integer> getTtsVolumes() {
|
||||
return ttsVolumes;
|
||||
}
|
||||
|
||||
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>") && speak.endsWith("</speak>")) ? "ssml" : "text";
|
||||
return announcement;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.connection;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
|
||||
/**
|
||||
* The {@link LoginData} holds the login data and provides the methods for serialization and deserialization
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LoginData {
|
||||
private static final String DEVICE_TYPE = "A2IVLV5VM2W81";
|
||||
|
||||
private final Random rand = new Random();
|
||||
private final CookieManager cookieManager;
|
||||
|
||||
// data fields
|
||||
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<Cookie> cookies = new ArrayList<>();
|
||||
|
||||
public LoginData(CookieManager cookieManager, String deviceId, String frc, String serial) {
|
||||
this.cookieManager = cookieManager;
|
||||
this.frc = frc;
|
||||
this.serial = serial;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public LoginData(CookieManager cookieManager) {
|
||||
this.cookieManager = cookieManager;
|
||||
|
||||
// FRC
|
||||
byte[] frcBinary = new byte[313];
|
||||
rand.nextBytes(frcBinary);
|
||||
this.frc = Base64.getEncoder().encodeToString(frcBinary);
|
||||
|
||||
// Serial number
|
||||
byte[] serialBinary = new byte[16];
|
||||
rand.nextBytes(serialBinary);
|
||||
this.serial = HexUtils.bytesToHex(serialBinary);
|
||||
|
||||
// Device id 16 random bytes in upper-case hex format, a # as separator and a fixed DEVICE_TYPE
|
||||
byte[] bytes = new byte[16];
|
||||
rand.nextBytes(bytes);
|
||||
String hexStr = HexUtils.bytesToHex(bytes).toUpperCase() + "#" + DEVICE_TYPE;
|
||||
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("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(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).toList();
|
||||
builder.append(cookies.size()).append("\n");
|
||||
cookies.forEach(cookie -> builder.append(cookie.serialize()));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public boolean deserialize(String data) {
|
||||
Scanner scanner = new Scanner(data);
|
||||
String version = scanner.nextLine();
|
||||
// check if serialize version is supported
|
||||
if (!"7".equals(version) && !"8".equals(version)) {
|
||||
scanner.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
frc = scanner.nextLine();
|
||||
serial = scanner.nextLine();
|
||||
deviceId = scanner.nextLine();
|
||||
|
||||
refreshToken = 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));
|
||||
}
|
||||
scanner.close();
|
||||
|
||||
CookieStore cookieStore = cookieManager.getCookieStore();
|
||||
cookieStore.removeAll();
|
||||
cookies.forEach(cookie -> cookieStore.add(null, cookie.toHttpCookie()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class Cookie {
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String comment;
|
||||
private final String commentURL;
|
||||
private final String domain;
|
||||
private final long maxAge;
|
||||
private final String path;
|
||||
private final String portlist;
|
||||
private final int version;
|
||||
private final boolean secure;
|
||||
private final boolean discard;
|
||||
|
||||
private Cookie(String name, String value, String comment, String commentURL, String domain, long maxAge,
|
||||
String path, String portlist, int version, boolean secure, boolean discard) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.comment = comment;
|
||||
this.commentURL = commentURL;
|
||||
this.domain = domain;
|
||||
this.maxAge = maxAge;
|
||||
this.path = path;
|
||||
this.portlist = portlist;
|
||||
this.version = version;
|
||||
this.secure = secure;
|
||||
this.discard = discard;
|
||||
}
|
||||
|
||||
private static String readValue(Scanner scanner) {
|
||||
if (scanner.nextLine().equals("1")) {
|
||||
return Objects.requireNonNullElse(scanner.nextLine(), "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void writeValue(StringBuilder builder, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
builder.append("0\n");
|
||||
} else {
|
||||
builder.append("1").append("\n").append(value).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
public static Cookie fromScanner(Scanner scanner) {
|
||||
return new Cookie(readValue(scanner), readValue(scanner), readValue(scanner), readValue(scanner),
|
||||
readValue(scanner), Long.parseLong(readValue(scanner)), readValue(scanner), readValue(scanner),
|
||||
Integer.parseInt(readValue(scanner)), Boolean.parseBoolean(readValue(scanner)),
|
||||
Boolean.parseBoolean(readValue(scanner)));
|
||||
}
|
||||
|
||||
public String serialize() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
writeValue(builder, name);
|
||||
writeValue(builder, value);
|
||||
writeValue(builder, comment);
|
||||
writeValue(builder, commentURL);
|
||||
writeValue(builder, domain);
|
||||
writeValue(builder, maxAge);
|
||||
writeValue(builder, path);
|
||||
writeValue(builder, portlist);
|
||||
writeValue(builder, version);
|
||||
writeValue(builder, secure);
|
||||
writeValue(builder, discard);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Cookie fromHttpCookie(HttpCookie cookie) {
|
||||
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getComment(), cookie.getCommentURL(),
|
||||
cookie.getDomain(), cookie.getMaxAge(), cookie.getPath(), cookie.getPortlist(), cookie.getVersion(),
|
||||
cookie.getSecure(), cookie.getDiscard());
|
||||
}
|
||||
|
||||
public HttpCookie toHttpCookie() {
|
||||
HttpCookie clientCookie = new HttpCookie(name, value);
|
||||
clientCookie.setComment(comment);
|
||||
clientCookie.setCommentURL(commentURL);
|
||||
clientCookie.setDomain(domain);
|
||||
clientCookie.setMaxAge(maxAge);
|
||||
clientCookie.setPath(path);
|
||||
clientCookie.setPortlist(portlist);
|
||||
clientCookie.setVersion(version);
|
||||
clientCookie.setSecure(secure);
|
||||
clientCookie.setDiscard(discard);
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.connection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.DeviceTO;
|
||||
|
||||
/**
|
||||
* The {@link TextWrapper} is a wrapper class for text or TTS instructions
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TextWrapper {
|
||||
private final List<DeviceTO> devices = new ArrayList<>();
|
||||
private final String text;
|
||||
private final List<@Nullable Integer> ttsVolumes = new ArrayList<>();
|
||||
private final List<@Nullable Integer> standardVolumes = new ArrayList<>();
|
||||
|
||||
public TextWrapper(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void add(DeviceTO device, @Nullable Integer ttsVolume, @Nullable Integer standardVolume) {
|
||||
devices.add(device);
|
||||
ttsVolumes.add(ttsVolume);
|
||||
standardVolumes.add(standardVolume);
|
||||
}
|
||||
|
||||
public List<DeviceTO> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
public List<@Nullable Integer> getTtsVolumes() {
|
||||
return ttsVolumes;
|
||||
}
|
||||
|
||||
public List<@Nullable Integer> getStandardVolumes() {
|
||||
return standardVolumes;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TextWrapper{" + "devices=" + devices + ", text='" + text + "'" + ", ttsVolumes=" + ttsVolumes
|
||||
+ ", standardVolumes=" + standardVolumes + "}";
|
||||
}
|
||||
}
|
|
@ -15,25 +15,26 @@ package org.openhab.binding.amazonechocontrol.internal.discovery;
|
|||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
|
||||
|
||||
import java.util.Date;
|
||||
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.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.connection.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.DeviceTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.EnabledFeedTO;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonDevices.Device;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
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.ServiceScope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -42,29 +43,20 @@ import org.slf4j.LoggerFactory;
|
|||
* the amazon account specified in the binding.
|
||||
*
|
||||
* @author Michael Geramb - Initial contribution
|
||||
* @author Jan N. Klug - Refactored to ThingHandlerService
|
||||
*/
|
||||
@Component(scope = ServiceScope.PROTOTYPE, service = AmazonEchoDiscovery.class)
|
||||
@NonNullByDefault
|
||||
public class AmazonEchoDiscovery extends AbstractDiscoveryService {
|
||||
|
||||
AccountHandler accountHandler;
|
||||
public class AmazonEchoDiscovery extends AbstractThingHandlerDiscoveryService<AccountHandler> {
|
||||
private static final int BACKGROUND_INTERVAL = 10; // in seconds
|
||||
private final Logger logger = LoggerFactory.getLogger(AmazonEchoDiscovery.class);
|
||||
private final Set<String> discoveredFlashBriefings = new HashSet<>();
|
||||
private final Set<List<EnabledFeedTO>> discoveredFlashBriefings = new HashSet<>();
|
||||
|
||||
private @Nullable ScheduledFuture<?> startScanStateJob;
|
||||
private @Nullable Long activateTimeStamp;
|
||||
|
||||
public AmazonEchoDiscovery(AccountHandler accountHandler) {
|
||||
super(SUPPORTED_ECHO_THING_TYPES_UIDS, 10);
|
||||
this.accountHandler = accountHandler;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
activate(new HashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
public AmazonEchoDiscovery() {
|
||||
super(AccountHandler.class, SUPPORTED_ECHO_THING_TYPES_UIDS, 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,36 +66,32 @@ public class AmazonEchoDiscovery extends AbstractDiscoveryService {
|
|||
if (activateTimeStamp != null) {
|
||||
removeOlderResults(activateTimeStamp);
|
||||
}
|
||||
setDevices(accountHandler.updateDeviceList());
|
||||
setDevices(thingHandler.updateDeviceList());
|
||||
|
||||
String currentFlashBriefingConfiguration = accountHandler.getNewCurrentFlashbriefingConfiguration();
|
||||
List<EnabledFeedTO> currentFlashBriefingConfiguration = thingHandler.updateFlashBriefingHandlers();
|
||||
discoverFlashBriefingProfiles(currentFlashBriefingConfiguration);
|
||||
}
|
||||
|
||||
protected void startAutomaticScan() {
|
||||
if (!this.accountHandler.getThing().getThings().isEmpty()) {
|
||||
if (!thingHandler.getThing().getThings().isEmpty()) {
|
||||
stopScanJob();
|
||||
return;
|
||||
}
|
||||
Connection connection = this.accountHandler.findConnection();
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
Date verifyTime = connection.tryGetVerifyTime();
|
||||
if (verifyTime == null) {
|
||||
return;
|
||||
}
|
||||
if (new Date().getTime() - verifyTime.getTime() < 10000) {
|
||||
Connection connection = thingHandler.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
|
||||
|
@ -121,43 +109,46 @@ public class AmazonEchoDiscovery extends AbstractDiscoveryService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Activate
|
||||
public void activate(@Nullable Map<String, Object> config) {
|
||||
super.activate(config);
|
||||
if (config != null) {
|
||||
modified(config);
|
||||
}
|
||||
public void initialize() {
|
||||
if (activateTimeStamp == null) {
|
||||
activateTimeStamp = new Date().getTime();
|
||||
}
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
synchronized void setDevices(List<Device> deviceList) {
|
||||
for (Device device : deviceList) {
|
||||
private synchronized void setDevices(List<DeviceTO> deviceList) {
|
||||
for (DeviceTO device : deviceList) {
|
||||
String serialNumber = device.serialNumber;
|
||||
if (serialNumber != null) {
|
||||
String deviceFamily = device.deviceFamily;
|
||||
if (deviceFamily != null) {
|
||||
ThingTypeUID thingTypeId;
|
||||
if ("ECHO".equals(deviceFamily)) {
|
||||
thingTypeId = THING_TYPE_ECHO;
|
||||
} else if ("ROOK".equals(deviceFamily)) {
|
||||
thingTypeId = THING_TYPE_ECHO_SPOT;
|
||||
} else if ("KNIGHT".equals(deviceFamily)) {
|
||||
thingTypeId = THING_TYPE_ECHO_SHOW;
|
||||
} else if ("WHA".equals(deviceFamily)) {
|
||||
thingTypeId = THING_TYPE_ECHO_WHA;
|
||||
} else {
|
||||
logger.debug("Unknown thing type '{}'", deviceFamily);
|
||||
continue;
|
||||
switch (deviceFamily) {
|
||||
case "ECHO":
|
||||
thingTypeId = THING_TYPE_ECHO;
|
||||
break;
|
||||
case "ROOK":
|
||||
thingTypeId = THING_TYPE_ECHO_SPOT;
|
||||
break;
|
||||
case "KNIGHT":
|
||||
thingTypeId = THING_TYPE_ECHO_SHOW;
|
||||
break;
|
||||
case "WHA":
|
||||
thingTypeId = THING_TYPE_ECHO_WHA;
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown thing type '{}'", deviceFamily);
|
||||
continue;
|
||||
}
|
||||
|
||||
ThingUID bridgeThingUID = this.accountHandler.getThing().getUID();
|
||||
ThingUID bridgeThingUID = thingHandler.getThing().getUID();
|
||||
ThingUID thingUID = new ThingUID(thingTypeId, bridgeThingUID, serialNumber);
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(device.accountName)
|
||||
.withProperty(DEVICE_PROPERTY_SERIAL_NUMBER, serialNumber)
|
||||
.withProperty(DEVICE_PROPERTY_FAMILY, deviceFamily)
|
||||
.withProperty(DEVICE_PROPERTY_DEVICE_TYPE_ID,
|
||||
Objects.requireNonNullElse(device.deviceType, "<unknown>"))
|
||||
.withRepresentationProperty(DEVICE_PROPERTY_SERIAL_NUMBER).withBridge(bridgeThingUID)
|
||||
.build();
|
||||
|
||||
|
@ -170,27 +161,21 @@ public class AmazonEchoDiscovery extends AbstractDiscoveryService {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized void discoverFlashBriefingProfiles(String currentFlashBriefingJson) {
|
||||
if (currentFlashBriefingJson.isEmpty()) {
|
||||
private synchronized void discoverFlashBriefingProfiles(List<EnabledFeedTO> enabledFeeds) {
|
||||
if (enabledFeeds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!discoveredFlashBriefings.contains(currentFlashBriefingJson)) {
|
||||
ThingUID bridgeThingUID = this.accountHandler.getThing().getUID();
|
||||
if (!discoveredFlashBriefings.contains(enabledFeeds)) {
|
||||
ThingUID bridgeThingUID = thingHandler.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)
|
||||
.withBridge(accountHandler.getThing().getUID()).build();
|
||||
logger.debug("Flash Briefing {} discovered", currentFlashBriefingJson);
|
||||
.withProperty(DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE, enabledFeeds)
|
||||
.withBridge(thingHandler.getThing().getUID()).build();
|
||||
logger.debug("Flash Briefing {} discovered", enabledFeeds);
|
||||
thingDiscovered(result);
|
||||
discoveredFlashBriefings.add(currentFlashBriefingJson);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removeExistingFlashBriefingProfile(@Nullable String currentFlashBriefingJson) {
|
||||
if (currentFlashBriefingJson != null) {
|
||||
discoveredFlashBriefings.remove(currentFlashBriefingJson);
|
||||
discoveredFlashBriefings.add(enabledFeeds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,9 +14,7 @@ package org.openhab.binding.amazonechocontrol.internal.discovery;
|
|||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -26,125 +24,80 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.amazonechocontrol.internal.Connection;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDevice.DriverIdentity;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeDeviceAlias;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.JsonSmartHomeGroups.SmartHomeGroup;
|
||||
import org.openhab.binding.amazonechocontrol.internal.dto.smarthome.SmartHomeBaseDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDeviceAlias;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.DriverIdentity;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup;
|
||||
import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice;
|
||||
import org.openhab.binding.amazonechocontrol.internal.smarthome.Constants;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
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.ServiceScope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Lukas Knoeller - Initial contribution
|
||||
* @author Jan N. Klug - Refactored to ThingHandlerService
|
||||
*/
|
||||
@Component(scope = ServiceScope.PROTOTYPE, service = SmartHomeDevicesDiscovery.class)
|
||||
@NonNullByDefault
|
||||
public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
|
||||
private AccountHandler accountHandler;
|
||||
private Logger logger = LoggerFactory.getLogger(SmartHomeDevicesDiscovery.class);
|
||||
public class SmartHomeDevicesDiscovery extends AbstractThingHandlerDiscoveryService<AccountHandler> {
|
||||
private final Logger logger = LoggerFactory.getLogger(SmartHomeDevicesDiscovery.class);
|
||||
|
||||
private @Nullable ScheduledFuture<?> startScanStateJob;
|
||||
private @Nullable Long activateTimeStamp;
|
||||
private @Nullable ScheduledFuture<?> discoveryJob;
|
||||
|
||||
public SmartHomeDevicesDiscovery(AccountHandler accountHandler) {
|
||||
super(SUPPORTED_SMART_HOME_THING_TYPES_UIDS, 10);
|
||||
this.accountHandler = accountHandler;
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
activate(new Hashtable<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
public SmartHomeDevicesDiscovery() {
|
||||
super(AccountHandler.class, SUPPORTED_SMART_HOME_THING_TYPES_UIDS, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
stopScanJob();
|
||||
Long activateTimeStamp = this.activateTimeStamp;
|
||||
if (activateTimeStamp != null) {
|
||||
removeOlderResults(activateTimeStamp);
|
||||
}
|
||||
setSmartHomeDevices(accountHandler.updateSmartHomeDeviceList(false));
|
||||
}
|
||||
|
||||
protected void startAutomaticScan() {
|
||||
if (!this.accountHandler.getThing().getThings().isEmpty()) {
|
||||
stopScanJob();
|
||||
return;
|
||||
}
|
||||
Connection connection = this.accountHandler.findConnection();
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
Date verifyTime = connection.tryGetVerifyTime();
|
||||
if (verifyTime == null) {
|
||||
return;
|
||||
}
|
||||
if (new Date().getTime() - verifyTime.getTime() < 10000) {
|
||||
return;
|
||||
}
|
||||
startScan();
|
||||
setSmartHomeDevices(thingHandler.updateSmartHomeDeviceList(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startBackgroundDiscovery() {
|
||||
stopScanJob();
|
||||
startScanStateJob = scheduler.scheduleWithFixedDelay(this::startAutomaticScan, 3000, 1000,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
stopScanJob();
|
||||
}
|
||||
|
||||
void stopScanJob() {
|
||||
ScheduledFuture<?> currentStartScanStateJob = startScanStateJob;
|
||||
if (currentStartScanStateJob != null) {
|
||||
currentStartScanStateJob.cancel(false);
|
||||
startScanStateJob = null;
|
||||
}
|
||||
protected void stopScan() {
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
super.stopScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Activate
|
||||
public void activate(@Nullable Map<String, Object> config) {
|
||||
super.activate(config);
|
||||
if (config != null) {
|
||||
modified(config);
|
||||
}
|
||||
Long activateTimeStamp = this.activateTimeStamp;
|
||||
if (activateTimeStamp == null) {
|
||||
this.activateTimeStamp = new Date().getTime();
|
||||
protected void startBackgroundDiscovery() {
|
||||
ScheduledFuture<?> discoveryJob = this.discoveryJob;
|
||||
if (discoveryJob == null || discoveryJob.isCancelled()) {
|
||||
this.discoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 1, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void setSmartHomeDevices(List<SmartHomeBaseDevice> deviceList) {
|
||||
int smartHomeDeviceDiscoveryMode = accountHandler.getSmartHomeDevicesDiscoveryMode();
|
||||
@Override
|
||||
protected void stopBackgroundDiscovery() {
|
||||
ScheduledFuture<?> discoveryJob = this.discoveryJob;
|
||||
if (discoveryJob != null) {
|
||||
discoveryJob.cancel(true);
|
||||
this.discoveryJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void setSmartHomeDevices(List<SmartHomeBaseDevice> deviceList) {
|
||||
int smartHomeDeviceDiscoveryMode = thingHandler.getSmartHomeDevicesDiscoveryMode();
|
||||
if (smartHomeDeviceDiscoveryMode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Object smartHomeDevice : deviceList) {
|
||||
ThingUID bridgeThingUID = this.accountHandler.getThing().getUID();
|
||||
ThingUID bridgeThingUID = thingHandler.getThing().getUID();
|
||||
ThingUID thingUID = null;
|
||||
String deviceName = null;
|
||||
Map<String, Object> props = new HashMap<>();
|
||||
|
||||
if (smartHomeDevice instanceof SmartHomeDevice shd) {
|
||||
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) {
|
||||
|
@ -161,11 +114,11 @@ public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
|
|||
isSkillDevice = driverIdentity != null && "SKILL".equals(driverIdentity.namespace);
|
||||
|
||||
if (smartHomeDeviceDiscoveryMode == 1 && isSkillDevice) {
|
||||
// Connected through skill
|
||||
// Connected through skill and we want direct only
|
||||
continue;
|
||||
}
|
||||
if (smartHomeDeviceDiscoveryMode != 2 && "openHAB".equalsIgnoreCase(shd.manufacturerName)) {
|
||||
// OpenHAB device
|
||||
if (smartHomeDeviceDiscoveryMode == 2 && "openHAB".equalsIgnoreCase(shd.manufacturerName)) {
|
||||
// openHAB device and we want non-openHAB only
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -178,30 +131,45 @@ public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
|
|||
thingUID = new ThingUID(THING_TYPE_SMART_HOME_DEVICE, bridgeThingUID, entityId.replace(".", "-"));
|
||||
|
||||
List<JsonSmartHomeDeviceAlias> aliases = shd.aliases;
|
||||
if ("Amazon".equals(shd.manufacturerName) && driverIdentity != null
|
||||
&& "SonarCloudService".equals(driverIdentity.identifier)) {
|
||||
String manufacturerName = shd.manufacturerName;
|
||||
if (manufacturerName != null) {
|
||||
props.put(DEVICE_PROPERTY_MANUFACTURER_NAME, manufacturerName);
|
||||
}
|
||||
if (manufacturerName != null && manufacturerName.startsWith("Amazon")) {
|
||||
List<@Nullable String> interfaces = shd.getCapabilities().stream().map(c -> c.interfaceName)
|
||||
.collect(Collectors.toList());
|
||||
if (interfaces.contains("Alexa.AcousticEventSensor")) {
|
||||
.toList();
|
||||
if (driverIdentity != null && "SonarCloudService".equals(driverIdentity.identifier)) {
|
||||
if (interfaces.contains("Alexa.AcousticEventSensor")) {
|
||||
deviceName = "Alexa Guard on " + shd.friendlyName;
|
||||
} else if (interfaces.contains("Alexa.ColorController")) {
|
||||
deviceName = "Alexa Color Controller on " + shd.friendlyName;
|
||||
} else if (interfaces.contains("Alexa.PowerController")) {
|
||||
deviceName = "Alexa Plug on " + shd.friendlyName;
|
||||
} else {
|
||||
deviceName = "Unknown Device on " + shd.friendlyName;
|
||||
}
|
||||
} else if (driverIdentity != null
|
||||
&& "OnGuardSmartHomeBridgeService".equals(driverIdentity.identifier)) {
|
||||
deviceName = "Alexa Guard";
|
||||
} else if (driverIdentity != null && "AlexaBridge".equals(driverIdentity.namespace)
|
||||
&& interfaces.contains("Alexa.AcousticEventSensor")) {
|
||||
deviceName = "Alexa Guard on " + shd.friendlyName;
|
||||
} else if (interfaces.contains("Alexa.ColorController")) {
|
||||
deviceName = "Alexa Color Controller on " + shd.friendlyName;
|
||||
} else if (interfaces.contains("Alexa.PowerController")) {
|
||||
deviceName = "Alexa Plug on " + shd.friendlyName;
|
||||
} else if (interfaces.contains("Alexa.ThermostatController")) {
|
||||
deviceName = "Alexa Smart " + shd.friendlyName;
|
||||
} else {
|
||||
deviceName = "Unknown Device on " + shd.friendlyName;
|
||||
}
|
||||
} else if ("Amazon".equals(shd.manufacturerName) && driverIdentity != null
|
||||
&& "OnGuardSmartHomeBridgeService".equals(driverIdentity.identifier)) {
|
||||
deviceName = "Alexa Guard";
|
||||
} else if (aliases != null && !aliases.isEmpty() && aliases.get(0).friendlyName != null) {
|
||||
deviceName = aliases.get(0).friendlyName;
|
||||
} else {
|
||||
deviceName = shd.friendlyName;
|
||||
}
|
||||
props.put(DEVICE_PROPERTY_ID, id);
|
||||
List<JsonSmartHomeDevice.DeviceIdentifier> alexaDeviceIdentifierList = shd.alexaDeviceIdentifierList;
|
||||
if (alexaDeviceIdentifierList != null && !alexaDeviceIdentifierList.isEmpty()) {
|
||||
props.put(DEVICE_PROPERTY_DEVICE_IDENTIFIER_LIST,
|
||||
alexaDeviceIdentifierList.stream()
|
||||
.map(d -> d.dmsDeviceSerialNumber + " @ " + d.dmsDeviceTypeId)
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
} else if (smartHomeDevice instanceof SmartHomeGroup shg) {
|
||||
logger.trace("Found SmartHome device: {}", shg);
|
||||
|
||||
|
@ -210,7 +178,7 @@ public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService {
|
|||
// No id
|
||||
continue;
|
||||
}
|
||||
Set<SmartHomeDevice> supportedChildren = SmartHomeDeviceHandler.getSupportedSmartHomeDevices(shg,
|
||||
Set<JsonSmartHomeDevice> supportedChildren = SmartHomeDeviceHandler.getSupportedSmartHomeDevices(shg,
|
||||
deviceList);
|
||||
if (supportedChildren.isEmpty()) {
|
||||
// No children with a supported interface
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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 + "}";
|
||||
}
|
||||
}
|
|
@ -10,18 +10,13 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.jsons;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
package org.openhab.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<T> {
|
||||
public T operationPayload;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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<String> profiles = List.of();
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "BluetoothPairedDeviceTO{address='" + address + "', connected=" + connected + ", deviceClass='"
|
||||
+ deviceClass + "', friendlyName='" + friendlyName + "', profiles=" + profiles + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<String> 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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* The {@link EnabledFeedsTO} encapsulate the data for handling enabled feeds
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
public class EnabledFeedsTO {
|
||||
public List<EnabledFeedTO> enabledFeeds = List.of();
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "EnabledFeedsTO{enabledFeeds=" + enabledFeeds + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<BehaviorOperationValidationResultTO<PlaySearchPhraseTO>> VALIDATION_RESULT_TO_TYPE_TOKEN = (TypeToken<BehaviorOperationValidationResultTO<PlaySearchPhraseTO>>) 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
|
||||
+ "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
+ "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
+ "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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.openhab.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<String, Object>> MAP_TYPE_TOKEN = (TypeToken<Map<String, Object>>) TypeToken
|
||||
.getParameterized(Map.class, String.class, Object.class);
|
||||
|
||||
private TOMapper() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
public static Map<String, Object> 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.push;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.push;
|
||||
|
||||
/**
|
||||
* The {@link PushListItemChangeTO} encapsulates a PUSH_LIST_ITEM_CHANGE message
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
public class PushListItemChangeTO {
|
||||
public String listId;
|
||||
public String listItemId;
|
||||
public int version;
|
||||
public String eventName;
|
||||
public String destinationUserId;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<RenderingUpdateTO> 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 + "'}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<AnnouncementContentTO> 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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.request;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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<DeviceIdTO> devices = List.of();
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "AnnouncementTargetTO{customerId='" + customerId + "', devices=" + devices + "}";
|
||||
}
|
||||
}
|
|
@ -10,24 +10,24 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.jsons;
|
||||
package org.openhab.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.request;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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<CookieTO> webSiteCookies = List.of();
|
||||
public String domain = ".amazon.com";
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "AuthRegisterCookiesTO{webSiteCookies=" + webSiteCookies + ", domain='" + domain + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.request;
|
||||
|
||||
import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.API_VERSION;
|
||||
import static org.openhab.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<String> 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<String, String> userContextMap = Map.of();
|
||||
@SerializedName("requested_token_type")
|
||||
public List<String> 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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.request;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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<String, List<CookieTO>> cookies = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "ExchangeTokenTokensTO{cookies=" + cookies + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.request;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<String, Object> 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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
+ "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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<AccountTO>> LIST_TYPE_TOKEN = (TypeToken<List<AccountTO>>) 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
|
||||
+ "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.dto.response;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.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<AscendingAlarmModelTO> ascendingAlarmModelList = List.of();
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "AscendingAlarmModelsTO{ascendingAlarmModelList=" + ascendingAlarmModelList + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "'}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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 + "}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.amazonechocontrol.internal.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
|
||||
+ "'}";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue