From 68ec3fb995631a4db2ad06935e407b4dea0c3de9 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Tue, 7 Jul 2020 22:43:52 +0200 Subject: [PATCH] [amazonechocontrol] add support for smarthome devices (#7969) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix json parsing Improve error handling Remove unused Exception * Changes for new build system * Original * Bugfixed for new version and merge request * Added license information and author * Added contributor and readme information * Uncommented test comment for production use * Removed the waitForUpdate variable - not used * Added two configurations params for pulling interval and activating the smart bulbs * [amazonechocontrol] Formatting change Fix for Announcment Channel * Bugfixed discovery * Bugfixed wrong entries of the amazon echo devices as lights * [amazonechocontrol] Fix for Announcment Channel * Implemented recursive searching for devices, filtered smart plugs * Added smart plugs * Removed unused code, renamed new function and it's references * Added translation * Added documentation * Added capabilities to smart home device * Added dynamic channel adding Improve smart home discover and and add options for openHAB smarthome devices Handle on/off in percentage channel Smart Home Device Handling Update Smart Home Device Handling Move Polling Timer to Account Handler Remove polling from smart home handler Alexa guard support * [amazonechocontrol] Bugfix for login in Australia (#6034) Fix invalid device state Bugfix and docu for announcment Fix duplicate channel registration Fix updates Skill device detection Improve channel handling Color Name handling Single device update Color handling Handle Security Panel Controller Color handling Alexa.AcousticEventSensor added Code cleanup Alexa.TemperatureSensor Interface PowerLevelController, PercentageController Add readme Add To Do List Fix nullable warning in Announcment Fix nullable warning Prepare Release Notes SmartHome update groups Fix spelling Fix group initialization * Removed unused file * Removed unused file * Error fixes * codestyle * Fix issues, codestyle and refactoring * address review comment Co-authored-by: Lkn94 Co-authored-by: Michael Geramb Co-authored-by: Lukas Knöller Signed-off-by: Jan N. Klug --- .../README.md | 305 ++++- .../internal/AccountHandlerConfig.java | 28 + .../internal/AccountServlet.java | 76 +- .../AmazonEchoControlBindingConstants.java | 231 ++-- .../AmazonEchoControlHandlerFactory.java | 334 +++--- ...onEchoDynamicStateDescriptionProvider.java | 54 +- .../internal/Connection.java | 320 ++++-- .../internal/WebSocketConnection.java | 6 +- .../channelhandler/ChannelHandler.java | 2 + .../ChannelHandlerAnnouncement.java | 8 +- .../ChannelHandlerSendMessage.java | 16 +- .../discovery/AmazonEchoDiscovery.java | 77 +- .../discovery/SmartHomeDevicesDiscovery.java | 247 ++++ .../internal/handler/AccountHandler.java | 359 ++++-- .../internal/handler/EchoHandler.java | 110 +- .../handler/SmartHomeDeviceHandler.java | 388 +++++++ .../internal/jsons/JsonActivities.java | 11 +- .../jsons/JsonAnnouncementTarget.java | 3 +- .../internal/jsons/JsonAscendingAlarm.java | 2 +- .../internal/jsons/JsonColorTemperature.java | 28 + .../internal/jsons/JsonColors.java | 28 + .../jsons/JsonCommandPayloadPushActivity.java | 2 +- .../jsons/JsonCommandPayloadPushDevice.java | 2 +- .../jsons/JsonDeviceNotificationState.java | 2 +- .../jsons/JsonExchangeTokenResponse.java | 20 +- .../internal/jsons/JsonNetworkDetails.java | 26 + .../jsons/JsonRegisterAppRequest.java | 65 +- .../jsons/JsonRegisterAppResponse.java | 111 +- .../jsons/JsonRenewTokenResponse.java | 11 +- .../jsons/JsonSmartHomeCapabilities.java | 41 + .../jsons/JsonSmartHomeDeviceAlias.java | 33 + .../JsonSmartHomeDeviceNetworkState.java | 30 + .../internal/jsons/JsonSmartHomeDevices.java | 63 ++ .../jsons/JsonSmartHomeGroupIdentifiers.java | 29 + .../jsons/JsonSmartHomeGroupIdentity.java | 30 + .../internal/jsons/JsonSmartHomeGroups.java | 52 + .../internal/jsons/JsonSmartHomeTags.java | 29 + .../internal/jsons/JsonTokenResponse.java | 17 +- .../internal/jsons/JsonUsersMeResponse.java | 35 +- .../internal/jsons/JsonWebSiteCookie.java | 14 +- .../internal/jsons/SmartHomeBaseDevice.java | 29 + .../internal/smarthome/Constants.java | 58 + .../DynamicStateDescriptionSmartHome.java | 81 ++ .../smarthome/HandlerAcousticEventSensor.java | 113 ++ .../internal/smarthome/HandlerBase.java | 140 +++ .../HandlerBrightnessController.java | 143 +++ .../smarthome/HandlerColorController.java | 161 +++ .../HandlerColorTemperatureController.java | 162 +++ .../HandlerPercentageController.java | 142 +++ .../smarthome/HandlerPowerController.java | 109 ++ .../HandlerPowerLevelController.java | 144 +++ .../HandlerSecurityPanelController.java | 171 +++ .../smarthome/HandlerTemperatureSensor.java | 98 ++ ...tHomeDeviceStateGroupUpdateCalculator.java | 129 +++ .../resources/ESH-INF/binding/binding.xml | 2 +- .../i18n/amazonechocontrol_de.properties | 61 + .../resources/ESH-INF/thing/thing-types.xml | 1001 +++++++++-------- 57 files changed, 4751 insertions(+), 1238 deletions(-) create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountHandlerConfig.java rename bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/{statedescription => }/AmazonEchoDynamicStateDescriptionProvider.java (85%) create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColors.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapabilities.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevices.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/Constants.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/DynamicStateDescriptionSmartHome.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBase.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java create mode 100644 bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java diff --git a/bundles/org.openhab.binding.amazonechocontrol/README.md b/bundles/org.openhab.binding.amazonechocontrol/README.md index 36539453468be..c5ff4f32a55ad 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/README.md +++ b/bundles/org.openhab.binding.amazonechocontrol/README.md @@ -27,6 +27,20 @@ It provides features to control and view the current state of echo devices: - get information about the next alarm, reminder and timer - send a message to the echo devices +It also provides features to control devices connected to your echo: + +- turn on/off your lights +- change the color +- control groups of lights or just single bulbs +- receive the current state of the lights +- turn on/off smart plugs (e. g. OSRAM) + +Restrictions: + +- groups can't receive their current color (multiple colors are possible) +- devices can only receive their state every 60 seconds +- turning on/off (or switch color, change brightness) will send a request for every single bulb in a group + Some ideas what you can do in your home by using rules and other openHAB controlled devices: - Automatic turn on your amplifier and connect echo with bluetooth if the echo plays music @@ -41,26 +55,21 @@ Some ideas what you can do in your home by using rules and other openHAB control - Let Alexa say 'welcome' to you if you open the door - Implement own handling for voice commands in a rule - Change the equalizer settings depending on the bluetooth connection -- Turn on a light on your Alexa alarm time - -## Note +- Turn on a light on your alexa alarm time +- Activate or deactivate the Alexa Guard with presence detection -This binding uses the same API as the Web-Browser-Based Alexa site (alexa.amazon.de). -In other words, it simulates a user which is using the web page. -Unfortunately, the binding can get broken if Amazon change the web site. +With the possibility to control your lights you could do: -The binding is tested with amazon.de, amazon.fr, amazon.it, amazon.com and amazon.co.uk accounts, but should also work with all others. +- a scene-based configuration of your rooms +- connect single bulbs to functions of openhab +- simulate your presence at home +- automatically turn on your lights at the evening +- integrate your smart bulbs with rules -## Supported Things +## Binding Configuration -| Thing type id | Name | -|----------------------|---------------------------------------| -| account | Amazon Account | -| echo | Amazon Echo Device | -| echospot | Amazon Echo Spot Device | -| echoshow | Amazon Echo Show Device | -| wha | Amazon Echo Whole House Audio Control | -| flashbriefingprofile | Flash briefing profile | +The binding does not have any configuration. +The configuration of your amazon account must be done in the 'Amazon Account' device. ## First Steps @@ -75,18 +84,52 @@ If the device type is not known by the binding, the device will not be discovere But you can define any device listed in your Alexa app with the best matching existing device (e.g. echo). You will find the required serial number in settings of the device in the Alexa app. -## Binding Configuration +### Discover Smart Home Devices + +If you want to discover your smart home devices you need to activate it in the 'Amazon Account' thing. +Devices from other skills can be discovered too. +See section *Smart Home Devices* below for more information. + +## Account -The binding does not have any configuration. The configuration of your Amazon account must be done in the 'Amazon Account' device. -## Thing Configuration +### Account Thing + +#### Supported Thing Type + +| Thing type id | Name | +|----------------------|---------------------------------------| +| account | Amazon Account | + +#### Thing Configuration -The Amazon Account does not need any 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. | -### Amazon Devices +#### Channels -All Amazon devices (echo, echospot, echoshow, wha) needs the following configurations: +| Channel Type ID | Item Type | Access Mode | Thing Type | Description +|-----------------------|-------------|-------------|-------------------------------|------------------------------------------------------------------------------------------ +| sendMessage | String | W | account | Write Only! Sends a message to the Echo devices. + +## Echo Control (Control Echo devices from openHAB) + +### echo, echospot, echoshow, wha Things + +#### Supported Thing Type + +| Thing type id | Name | +|----------------------|---------------------------------------| +| echo | Amazon Echo Device | +| echospot | Amazon Echo Spot Device | +| echoshow | Amazon Echo Show Device | +| wha | Amazon Echo Whole House Audio Control | + +#### Thing Configuration | Configuration name | Description | |--------------------------|----------------------------------------------------| @@ -99,7 +142,7 @@ You will find the serial number in the Alexa app or on the webpage YOUR_OPENHAB/ The flashbriefingprofile thing has no configuration parameters. It will be configured at runtime by using the save channel to store the current flash briefing configuration in the thing. -## Channels +#### Channels | Channel Type ID | Item Type | Access Mode | Thing Type | Description |-----------------------|-------------|-------------|-------------------------------|------------------------------------------------------------------------------------------ @@ -124,7 +167,7 @@ It will be configured at runtime by using the save channel to store the current | amazonMusic | Switch | R/W | echo, echoshow, echospot, wha | Start playing of the last used Amazon Music song (works after at least one song was started after the openhab start) | remind | String | R/W | echo, echoshow, echospot | Write Only! Speak the reminder and sends a notification to the Alexa app (Currently the reminder is played and notified two times, this seems to be a bug in the Amazon software) | nextReminder | DateTime | R | echo, echoshow, echospot | Next reminder on the device -| playAlarmSound | String | W | echo, echoshow, echospot | Write Only! Plays ans Alarm sound +| 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 @@ -132,8 +175,8 @@ It will be configured at runtime by using the save channel to store the current | musicProviderId | String | R/W | echo, echoshow, echospot | Current Music provider | playMusicVoiceCommand | String | W | echo, echoshow, echospot | Write Only! Voice command as text. E.g. 'Yesterday from the Beatles' | startCommand | String | W | echo, echoshow, echospot | Write Only! Used to start anything. Available options: Weather, Traffic, GoodMorning, SingASong, TellStory, FlashBriefing and FlashBriefing. (Note: The options are case sensitive) -| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. Please note: the announcement feature must be activated in the alexa app at the echo device. See in the tutorial section to learn how it’s possible to set the title and turn off the sound. -| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `I want to tell you a secret.I am not a real human. Please note: the announcement feature must be activated in the alexa app at the echo device to use SSML. ` +| announcement | String | W | echo, echoshow, echospot | Write Only! Display the announcement message on the display. See in the tutorial section to learn how it’s possible to set the title and turn off the sound. +| textToSpeech | String | W | echo, echoshow, echospot | Write Only! Write some text to this channel and Alexa will speak it. It is possible to use plain text or SSML: e.g. `I want to tell you a secret.I am not a real human.` | textToSpeechVolume | Dimmer | R/W | echo, echoshow, echospot | Volume of the textToSpeech channel, if 0 the current volume will be used | 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 @@ -156,25 +199,21 @@ E.g. to read out the history call from an installation on openhab:8080 with an a http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1 -## Full Example +### Example -### amazonechocontrol.things +#### echo.things ``` -Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" +Bridge amazonechocontrol:account:account1 "Amazon Account" @ "Accounts" [discoverSmartHome=2, pollingIntervalSmartHomeAlexa=30, pollingIntervalSmartSkills=120] { Thing echo echo1 "Alexa" @ "Living Room" [serialNumber="SERIAL_NUMBER"] Thing echoshow echoshow1 "Alexa" @ "Kitchen" [serialNumber="SERIAL_NUMBER"] Thing echospot echospot1 "Alexa" @ "Sleeping Room" [serialNumber="SERIAL_NUMBER"] Thing wha wha1 "Ground Floor Music Group" @ "Music Groups" [serialNumber="SERIAL_NUMBER"] - Thing flashbriefingprofile flashbriefing1 "Flash Briefing Technical" @ "Flash Briefings" - Thing flashbriefingprofile flashbriefing2 "Flash Briefing Life Style" @ "Flash Briefings" } ``` -You will find the serial number in the Alexa app. - -### amazonechocontrol.items: +#### echo.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. @@ -222,6 +261,21 @@ Switch Echo_Living_Room_Bluetooth "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"} + +// 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"} 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"} @@ -238,7 +292,6 @@ DateTime Echo_Living_Room_NextAlarm "Next alarm" 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"} @@ -249,7 +302,7 @@ Switch FlashBriefing_LifeStyle_Active "Active" { channel="amazonechoc String FlashBriefing_LifeStyle_Play "Play (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing2:playOnDevice"} ``` -### amazonechocontrol.sitemap: +#### echo.sitemap: ``` sitemap amazonechocontrol label="Echo Devices" @@ -299,7 +352,62 @@ sitemap amazonechocontrol label="Echo Devices" Slider item=Echo_Living_Room_NotificationVolume Switch item=Echo_Living_Room_AscendingAlarm } +} +``` + +## Flash Briefing + +#### Supported Things + +| Thing type id | Name | +|----------------------|---------------------------------------| +| flashbriefingprofile | Flash briefing profile | + +#### Channels + +The flashbriefingprofile thing has no configuration parameters. +It will be configured at runtime by using the save channel to store the current flash briefing configuration which is set in the alexa app in the thing. Create a flashbriefingprofile Thing for each set you need. +E.g. One Flashbriefing profile with technical news and wheater, one for playing world news and one for sport news. + +| Channel Type ID | Item Type | Access Mode | Thing Type | Description +|-----------------------|-------------|-------------|-------------------------------|------------------------------------------------------------------------------------------ +| save | Switch | W | flashbriefingprofile | Write Only! Stores the current configuration of flash briefings within the thing +| active | Switch | R/W | flashbriefingprofile | Active the profile +| playOnDevice | String | W | flashbriefingprofile | Specify the echo serial number or name to start the flash briefing. + +#### Example + +#### flashbriefings.things + +``` +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: + +Sample for the Thing echo1 only. But it will work in the same way for the other things, only replace the thing name in the channel link. +Take a look in the channel description above to know, which channels are supported by your thing type. + +``` +// Flashbriefings +Switch FlashBriefing_Technical_Save "Save (Write only)" { channel="amazonechocontrol:flashbriefingprofile:account1:flashbriefing1:save"} +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: + +``` +sitemap flashbriefings label="Flash Briefings" +{ Frame label="Flash Briefing Technical" { Switch item=FlashBriefing_Technical_Save Switch item=FlashBriefing_Technical_Active @@ -314,6 +422,104 @@ sitemap amazonechocontrol label="Echo Devices" } ``` +## Smart Home Devices + +Note: the cannels 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. + +#### Supported Things + +| Thing type id | Name | +|----------------------|---------------------------------------| +| smartHomeDevice | Smart Home Device | +| smartHomeDeviceGroup | Smart Home Device group | + + +#### Thing configuration of smartHomeDevice, smartHomeDeviceGroup + +| Configuration name | Description | +|--------------------------|---------------------------------------------------------------------------| +| id | The id of the device or device group | + +The only possibility to find out the id is by using the discover function in the PaperUI. You can use then the id, if you want define the Thing in a file. + +#### Channels + +The channels of the smarthome devices will be generated at runtime. Check in the paperUI thing configurations, which channels are created. + +| Channel Type ID | Item Type | Access Mode | Thing Type | Description +|--------------------------|-----------|-------------|-------------------------------|------------------------------------------------------------------------------------------ +| powerState | Switch | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the state (ON/OFF) of your device +| brightness | Dimmer | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the brightness of your lamp +| color | Color | R | smartHomeDevice, smartHomeDeviceGroup | Shows the color of your light +| colorName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | Shows and changes the color name of your light (groups are not able to show their color) +| colorTemperatureName | String | R/W | smartHomeDevice, smartHomeDeviceGroup | White temperatures name of your lights (groups are not able to show their color) +| armState | String | R/W | smartHomeDevice, smartHomeDeviceGroup | State of your alarm guard. Options: ARMED_AWAY, ARMED_STAY, ARMED_NIGHT, DISARMED (groups are not able to show their state) +| burglaryAlarm | Contact | R | smartHomeDevice | Burglary alarm +| carbonMonoxideAlarm | Contact | R | smartHomeDevice | Carbon monoxide detection alarm +| fireAlarm | Contact | R | smartHomeDevice | Fire alarm +| waterAlarm | Contact | R | smartHomeDevice | Water alarm +| glassBreakDetectionState | Contact | R | smartHomeDevice | Glass break detection alarm +| smokeAlarmDetectionState | Contact | R | smartHomeDevice | Smoke detection alarm +| temperature | Number | R | smartHomeDevice | Temperature + +### Example + +#### smarthome.things + +``` +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. + +``` +// 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 in the PaperUI. + +#### smarthome.sitemap: + +``` +sitemap smarthome label="Smart Home Devices" +{ + Frame label="Lights and light groups" { + Switch item=Light_State + Slider item=Light_Brightness + Default item=Light_Color + Selection item=Light_Color_Name mappings=[ ''='', 'red'='Red', 'crimson'='Crimson', 'salmon'='Salmon', 'orange'='Orange', 'gold'='Gold', 'yellow'='Yellow', 'green'='Green', 'turquoise'='Turquoise', 'cyan'='Cyan', 'sky_blue'='Sky Blue', 'blue'='Blue', 'purple'='Purple', 'magenta'='Magenta', 'pink'='Pink', 'lavender'='Lavender' ] + Selection item=Light_White mappings=[ ''='', 'warm_white'='Warm white', 'soft_white'='Soft white', 'white'='White', 'daylight_white'='Daylight white', 'cool_white'='Cool white' ] + Switch item=Light_State + Switch item=Group_State + Selection item=Arm_State mappings=[ 'ARMED_AWAY'='Active', 'ARMED_STAY'='Present', 'ARMED_NIGHT'='Night', 'DISARMED'='Deactivated' ] + } +} +``` + ## How To Get IDs 1) Open the url YOUR_OPENHAB/amazonechocontrol in your browser (e.g. http://openhab:8080/amazonechocontrol/) @@ -321,6 +527,14 @@ sitemap amazonechocontrol label="Echo Devices" 3) Click on the name of the echo thing 4) Scroll to the channel and copy the required ID +## Advanced Feature Technically Experienced Users + +The url /amazonechocontrol//PROXY/ provides a proxy server with an authenticated connection to the amazon alexa server. 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 a account named account1: + +http://openhab:8080/amazonechocontrol/account1/PROXY/api/activities?startTime=&size=50&offset=1 + ## Tutorials ### Let Alexa speak a text from a rule: @@ -363,6 +577,7 @@ end ``` Expert: + You can use a json formatted string to control title, sound and volume: ```php @@ -377,6 +592,7 @@ 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. + ```php rule "Say welcome if the door opens" when @@ -463,6 +679,23 @@ then end ``` +## Advanced Feature Technically Experienced Users + +The url /amazonechocontrol//PROXY/ provides a proxy server with an authenticated connection to the Amazon Alexa server. +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 + +## Note + +This binding uses the same API as the Web-Browser-Based Alexa site (alexa.amazon.de). +In other words, it simulates a user which is using the web page. +Unfortunately, this binding can break if Amazon changes the web site. + +The binding is tested with amazon.de, amazon.fr, amazon.it, amazon.com and amazon.co.uk accounts, but should also work with all others. + ## Credits The idea for writing this binding came from this blog: https://blog.loetzimmer.de/2017/10/amazon-alexa-hort-auf-die-shell-echo.html (German). diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountHandlerConfig.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountHandlerConfig.java new file mode 100644 index 0000000000000..f5476e352cc15 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountHandlerConfig.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 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.handler.AccountHandler; + +/** + * The {@link AccountHandlerConfig} holds the configuration for the {@link AccountHandler} + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public class AccountHandlerConfig { + public int discoverSmartHome = 0; + public int pollingIntervalSmartHomeAlexa = 60; + public int pollingIntervalSmartSkills = 120; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java index 41d4bceae4255..27ff048879868 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AccountServlet.java @@ -33,7 +33,6 @@ import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.thing.Thing; @@ -55,7 +54,6 @@ 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 @@ -76,33 +74,27 @@ public class AccountServlet extends HttpServlet { private final Logger logger = LoggerFactory.getLogger(AccountServlet.class); - final HttpService httpService; - String servletUrlWithoutRoot; - final String servletUrl; - AccountHandler account; - String id; - @Nullable - Connection connectionToInitialize; - final Gson gson; + 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, "UTF8"); - } catch (UnsupportedEncodingException e) { - servletUrlWithoutRoot = ""; - servletUrl = ""; - logger.warn("Register servlet fails", e); - return; - } - servletUrl = "/" + servletUrlWithoutRoot; - try { + servletUrl = "/" + servletUrlWithoutRoot; + httpService.registerServlet(servletUrl, this, null, httpService.createDefaultHttpContext()); - } catch (NamespaceException | ServletException e) { - logger.warn("Register servlet fails", e); + } catch (UnsupportedEncodingException | NamespaceException | ServletException e) { + throw new IllegalStateException(e.getMessage()); } } @@ -565,7 +557,7 @@ private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device de } if (playLists != null) { - Map<@NonNull String, @Nullable PlayList @Nullable []> playlistMap = playLists.playlists; + Map playlistMap = playLists.playlists; if (playlistMap != null && !playlistMap.isEmpty()) { html.append(""); @@ -573,7 +565,7 @@ private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device de { if (innerLists != null && innerLists.length > 0) { PlayList playList = innerLists[0]; - if (playList.playlistId != null && playList.title != null) { + if (playList != null && playList.playlistId != null && playList.title != null) { html.append("
NameValue
"); html.append(StringEscapeUtils.escapeHtml(nullReplacement(playList.title))); html.append(""); @@ -593,24 +585,32 @@ private void renderAmazonMusicPlaylistIdChannel(Connection connection, Device de private void renderBluetoothMacChannel(Connection connection, Device device, StringBuilder html) { html.append("

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

"); JsonBluetoothStates bluetoothStates = connection.getBluetoothConnectionStates(); + if (bluetoothStates == null) { + return; + } BluetoothState[] innerStates = bluetoothStates.bluetoothStates; - if (innerStates != null) { - for (BluetoothState state : innerStates) { - if (StringUtils.equals(state.deviceSerialNumber, device.serialNumber)) { - PairedDevice[] pairedDeviceList = state.pairedDeviceList; - if (pairedDeviceList != null && pairedDeviceList.length > 0) { - html.append(""); - for (PairedDevice pairedDevice : pairedDeviceList) { - html.append(""); - } - html.append("
NameValue
"); - html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.friendlyName))); - html.append(""); - html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.address))); - html.append("
"); - } else { - html.append(StringEscapeUtils.escapeHtml("No bluetooth devices paired")); + if (innerStates == null) { + return; + } + for (BluetoothState state : innerStates) { + if (state == null) { + continue; + } + if ((state.deviceSerialNumber == null && device.serialNumber == null) + || (state.deviceSerialNumber != null && state.deviceSerialNumber.equals(device.serialNumber))) { + PairedDevice[] pairedDeviceList = state.pairedDeviceList; + if (pairedDeviceList != null && pairedDeviceList.length > 0) { + html.append(""); + for (PairedDevice pairedDevice : pairedDeviceList) { + html.append(""); } + html.append("
NameValue
"); + html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.friendlyName))); + html.append(""); + html.append(StringEscapeUtils.escapeHtml(nullReplacement(pairedDevice.address))); + html.append("
"); + } else { + html.append(StringEscapeUtils.escapeHtml("No bluetooth devices paired")); } } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java index 9cef8ad28a221..43132e27eeb74 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlBindingConstants.java @@ -1,112 +1,119 @@ -/** - * Copyright (c) 2010-2020 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.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; - -/** - * The {@link AmazonEchoControlBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Michael Geramb - Initial contribution - */ -@NonNullByDefault -public class AmazonEchoControlBindingConstants { - - public static final String BINDING_ID = "amazonechocontrol"; - public static final String BINDING_NAME = "Amazon Echo Control"; - - // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); - public static final ThingTypeUID THING_TYPE_ECHO = new ThingTypeUID(BINDING_ID, "echo"); - 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 Set SUPPORTED_THING_TYPES_UIDS = new HashSet<>( - Arrays.asList(THING_TYPE_ACCOUNT, THING_TYPE_ECHO, THING_TYPE_ECHO_SPOT, THING_TYPE_ECHO_SHOW, - THING_TYPE_ECHO_WHA, THING_TYPE_FLASH_BRIEFING_PROFILE)); - - // List of all Channel ids - 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"; - public static final String CHANNEL_SUBTITLE2 = "subtitle2"; - public static final String CHANNEL_PROVIDER_DISPLAY_NAME = "providerDisplayName"; - 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_REMIND = "remind"; - public static final String CHANNEL_PLAY_ALARM_SOUND = "playAlarmSound"; - public static final String CHANNEL_START_ROUTINE = "startRoutine"; - public static final String CHANNEL_MUSIC_PROVIDER_ID = "musicProviderId"; - 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_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_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_FLASH_BRIEFING_PROFILE = "configurationJson"; - - // Other - public static final String FLASH_BRIEFING_COMMAND_PREFIX = "FlashBriefing."; -} +/** + * Copyright (c) 2010-2020 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.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; + +/** + * The {@link AmazonEchoControlBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class AmazonEchoControlBindingConstants { + public static final String BINDING_ID = "amazonechocontrol"; + public static final String BINDING_NAME = "Amazon Echo Control"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); + public static final ThingTypeUID THING_TYPE_ECHO = new ThingTypeUID(BINDING_ID, "echo"); + 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 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 SUPPORTED_SMART_HOME_THING_TYPES_UIDS = new HashSet<>( + Arrays.asList(THING_TYPE_SMART_HOME_DEVICE, THING_TYPE_SMART_HOME_DEVICE_GROUP)); + + // List of all Channel ids + 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"; + public static final String CHANNEL_SUBTITLE2 = "subtitle2"; + public static final String CHANNEL_PROVIDER_DISPLAY_NAME = "providerDisplayName"; + 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_REMIND = "remind"; + public static final String CHANNEL_PLAY_ALARM_SOUND = "playAlarmSound"; + public static final String CHANNEL_START_ROUTINE = "startRoutine"; + public static final String CHANNEL_MUSIC_PROVIDER_ID = "musicProviderId"; + 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_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_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_FLASH_BRIEFING_PROFILE = "configurationJson"; + public static final String DEVICE_PROPERTY_ID = "id"; + + // Other + public static final String FLASH_BRIEFING_COMMAND_PREFIX = "FlashBriefing."; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java index 69bb3d816e778..91d3ae019a8c0 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoControlHandlerFactory.java @@ -1,177 +1,157 @@ -/** - * Copyright (c) 2010-2020 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.HashMap; -import java.util.Hashtable; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.config.discovery.DiscoveryService; -import org.eclipse.smarthome.core.storage.Storage; -import org.eclipse.smarthome.core.storage.StorageService; -import org.eclipse.smarthome.core.thing.Bridge; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; -import org.eclipse.smarthome.core.thing.binding.ThingHandler; -import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; -import org.openhab.binding.amazonechocontrol.internal.discovery.AmazonEchoDiscovery; -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.osgi.framework.ServiceRegistration; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.osgi.service.http.HttpService; - -import com.google.gson.Gson; - -/** - * The {@link AmazonEchoControlHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Michael Geramb - Initial contribution - */ -@Component(service = ThingHandlerFactory.class, configurationPid = "binding.amazonechocontrol") -@NonNullByDefault -public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory { - - private final Map> discoveryServiceRegistrations = new HashMap<>(); - - @Nullable - HttpService httpService; - @Nullable - StorageService storageService; - @Nullable - BindingServlet bindingServlet; - @Nullable - Gson gson; - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - HttpService httpService = this.httpService; - if (bindingServlet == null && httpService != null) { - bindingServlet = new BindingServlet(httpService); - } - } - - @Override - protected void deactivate(ComponentContext componentContext) { - BindingServlet bindingServlet = this.bindingServlet; - this.bindingServlet = null; - if (bindingServlet != null) { - bindingServlet.dispose(); - } - super.deactivate(componentContext); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - HttpService httpService = this.httpService; - if (httpService == null) { - return null; - } - StorageService storageService = this.storageService; - if (storageService == null) { - return null; - } - Gson gson = this.gson; - if (gson == null) { - gson = new Gson(); - this.gson = gson; - } - - if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { - Storage storage = storageService.getStorage(thing.getUID().toString(), - String.class.getClassLoader()); - AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage, gson); - registerDiscoveryService(bridgeHandler); - BindingServlet bindingServlet = this.bindingServlet; - if (bindingServlet != null) { - bindingServlet.addAccountThing(thing); - } - return bridgeHandler; - } - if (thingTypeUID.equals(THING_TYPE_FLASH_BRIEFING_PROFILE)) { - Storage storage = storageService.getStorage(thing.getUID().toString(), - String.class.getClassLoader()); - return new FlashBriefingProfileHandler(thing, storage); - } - if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { - return new EchoHandler(thing, gson); - } - return null; - } - - private synchronized void registerDiscoveryService(AccountHandler bridgeHandler) { - AmazonEchoDiscovery discoveryService = new AmazonEchoDiscovery(bridgeHandler); - discoveryService.activate(); - this.discoveryServiceRegistrations.put(bridgeHandler.getThing().getUID(), - bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); - } - - @Override - protected synchronized void removeHandler(ThingHandler thingHandler) { - if (thingHandler instanceof AccountHandler) { - BindingServlet bindingServlet = this.bindingServlet; - if (bindingServlet != null) { - bindingServlet.removeAccountThing(thingHandler.getThing()); - } - - ServiceRegistration serviceReg = this.discoveryServiceRegistrations - .remove(thingHandler.getThing().getUID()); - if (serviceReg != null) { - // remove discovery service, if bridge handler is removed - AmazonEchoDiscovery service = (AmazonEchoDiscovery) bundleContext.getService(serviceReg.getReference()); - serviceReg.unregister(); - if (service != null) { - service.deactivate(); - } - } - } - } - - @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC) - protected void setHttpService(HttpService httpService) { - this.httpService = httpService; - } - - protected void unsetHttpService(HttpService httpService) { - this.httpService = null; - } - - @Reference - protected void setStorageService(StorageService storageService) { - this.storageService = storageService; - } - - protected void unsetStorageService(StorageService storageService) { - this.storageService = null; - } -} +/** + * Copyright (c) 2010-2020 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.SUPPORTED_ECHO_THING_TYPES_UIDS; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.SUPPORTED_SMART_HOME_THING_TYPES_UIDS; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_ACCOUNT; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_FLASH_BRIEFING_PROFILE; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryService; +import org.eclipse.smarthome.core.storage.Storage; +import org.eclipse.smarthome.core.storage.StorageService; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.openhab.binding.amazonechocontrol.internal.discovery.AmazonEchoDiscovery; +import org.openhab.binding.amazonechocontrol.internal.discovery.SmartHomeDevicesDiscovery; +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.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.Reference; +import org.osgi.service.http.HttpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +/** + * The {@link AmazonEchoControlHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Michael Geramb - Initial contribution + */ +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.amazonechocontrol") +@NonNullByDefault +public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory { + private final Logger logger = LoggerFactory.getLogger(AmazonEchoControlHandlerFactory.class); + private final Map>> discoveryServiceRegistrations = new HashMap<>(); + + private final HttpService httpService; + private final StorageService storageService; + private final BindingServlet bindingServlet; + private final Gson gson; + + @Activate + public AmazonEchoControlHandlerFactory(@Reference HttpService httpService, + @Reference StorageService storageService) { + this.storageService = storageService; + this.httpService = httpService; + this.gson = new Gson(); + this.bindingServlet = new BindingServlet(httpService); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_ECHO_THING_TYPES_UIDS.contains(thingTypeUID) + || SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected void deactivate(ComponentContext componentContext) { + bindingServlet.dispose(); + super.deactivate(componentContext); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { + Storage storage = storageService.getStorage(thing.getUID().toString(), + String.class.getClassLoader()); + AccountHandler bridgeHandler = new AccountHandler((Bridge) thing, httpService, storage, gson); + registerDiscoveryService(bridgeHandler); + bindingServlet.addAccountThing(thing); + return bridgeHandler; + } else if (thingTypeUID.equals(THING_TYPE_FLASH_BRIEFING_PROFILE)) { + Storage storage = storageService.getStorage(thing.getUID().toString(), + String.class.getClassLoader()); + return new FlashBriefingProfileHandler(thing, storage); + } else if (SUPPORTED_ECHO_THING_TYPES_UIDS.contains(thingTypeUID)) { + return new EchoHandler(thing, gson); + } else if (SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID)) { + return new SmartHomeDeviceHandler(thing, gson); + } + return null; + } + + private synchronized void registerDiscoveryService(AccountHandler bridgeHandler) { + List> discoveryServiceRegistration = 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) { + if (thingHandler instanceof AccountHandler) { + BindingServlet bindingServlet = this.bindingServlet; + bindingServlet.removeAccountThing(thingHandler.getThing()); + + List> 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) { + ((AmazonEchoDiscovery) service).deactivate(); + } else if (service instanceof SmartHomeDevicesDiscovery) { + ((SmartHomeDevicesDiscovery) service).deactivate(); + } else { + logger.warn("Found unknown discovery-service instance: {}", service); + } + } + }); + } + } + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/statedescription/AmazonEchoDynamicStateDescriptionProvider.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoDynamicStateDescriptionProvider.java similarity index 85% rename from bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/statedescription/AmazonEchoDynamicStateDescriptionProvider.java rename to bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoDynamicStateDescriptionProvider.java index 93cc6c5a27af5..807439a1cf6ac 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/statedescription/AmazonEchoDynamicStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/AmazonEchoDynamicStateDescriptionProvider.java @@ -10,9 +10,16 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.amazonechocontrol.internal.statedescription; +package org.openhab.binding.amazonechocontrol.internal; -import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_ID; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_AMAZON_MUSIC_PLAY_LIST_ID; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_BLUETHOOTH_MAC; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_CHANNEL_PLAY_ON_DEVICE; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_MUSIC_PROVIDER_ID; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_PLAY_ALARM_SOUND; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.CHANNEL_TYPE_START_COMMAND; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.FLASH_BRIEFING_COMMAND_PREFIX; import java.util.ArrayList; import java.util.Arrays; @@ -28,11 +35,11 @@ import org.eclipse.smarthome.core.thing.ThingRegistry; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.eclipse.smarthome.core.thing.type.DynamicStateDescriptionProvider; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.StateDescriptionFragmentBuilder; import org.eclipse.smarthome.core.types.StateOption; -import org.openhab.binding.amazonechocontrol.internal.Connection; import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler; import org.openhab.binding.amazonechocontrol.internal.handler.EchoHandler; import org.openhab.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler; @@ -43,10 +50,9 @@ 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.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; /** * Dynamic channel state description provider. @@ -57,23 +63,14 @@ @Component(service = { DynamicStateDescriptionProvider.class, AmazonEchoDynamicStateDescriptionProvider.class }) @NonNullByDefault public class AmazonEchoDynamicStateDescriptionProvider implements DynamicStateDescriptionProvider { + private final ThingRegistry thingRegistry; - private @Nullable ThingRegistry thingRegistry; - - @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC) - protected void setThingRegistry(ThingRegistry thingRegistry) { - this.thingRegistry = thingRegistry; - } - - protected void unsetThingRegistry(ThingRegistry thingRegistry) { + @Activate + public AmazonEchoDynamicStateDescriptionProvider(@Reference ThingRegistry thingRegistry) { this.thingRegistry = thingRegistry; } public @Nullable ThingHandler findHandler(Channel channel) { - ThingRegistry thingRegistry = this.thingRegistry; - if (thingRegistry == null) { - return null; - } Thing thing = thingRegistry.get(channel.getUID().getThingUID()); if (thing == null) { return null; @@ -100,13 +97,14 @@ protected void unsetThingRegistry(ThingRegistry thingRegistry) { @Override public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription originalStateDescription, @Nullable Locale locale) { - if (originalStateDescription == null) { + ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); + if (channelTypeUID == null || !BINDING_ID.equals(channelTypeUID.getBindingId())) { return null; } - ThingRegistry thingRegistry = this.thingRegistry; - if (thingRegistry == null) { + if (originalStateDescription == null) { return null; } + if (CHANNEL_TYPE_BLUETHOOTH_MAC.equals(channel.getChannelTypeUID())) { EchoHandler handler = (EchoHandler) findHandler(channel); if (handler == null) { @@ -135,7 +133,6 @@ protected void unsetThingRegistry(ThingRegistry thingRegistry) { 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) { @@ -214,9 +211,8 @@ protected void unsetThingRegistry(ThingRegistry thingRegistry) { options.add(new StateOption(value, device.accountName)); } } - StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription) - .withOptions(options).build().toStateDescription(); - return result; + 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) { @@ -240,9 +236,8 @@ protected void unsetThingRegistry(ThingRegistry thingRegistry) { options.add(new StateOption(providerId, displayName)); } } - StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription) - .withOptions(options).build().toStateDescription(); - return result; + 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) { @@ -265,9 +260,8 @@ protected void unsetThingRegistry(ThingRegistry thingRegistry) { String displayName = flashBriefing.getThing().getLabel(); options.add(new StateOption(value, displayName)); } - StateDescription result = StateDescriptionFragmentBuilder.create(originalStateDescription) - .withOptions(options).build().toStateDescription(); - return result; + return StateDescriptionFragmentBuilder.create(originalStateDescription).withOptions(options).build() + .toStateDescription(); } return null; } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java index 51c1ebfed0371..237c339c0249b 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/Connection.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -36,6 +37,7 @@ import java.util.Map; import java.util.Random; import java.util.Scanner; +import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -73,6 +75,7 @@ import org.openhab.binding.amazonechocontrol.internal.jsons.JsonFeed; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMediaState; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonMusicProvider; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNetworkDetails; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationRequest; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationResponse; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; @@ -91,37 +94,39 @@ import org.openhab.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Success; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonRegisterAppResponse.Tokens; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonRenewTokenResponse; +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.JsonStartRoutineRequest; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonUsersMeResponse; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonWakeWords; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonWakeWords.WakeWord; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonWebSiteCookie; +import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSyntaxException; /** - * The {@link Connection} is responsible for the connection to the amazon server and - * handling of the commands + * The {@link Connection} is responsible for the connection to the amazon server + * and handling of the commands * * @author Michael Geramb - Initial contribution */ @NonNullByDefault public class Connection { - private static final String THING_THREADPOOL_NAME = "thingHandler"; + private static final long EXPIRES_IN = 432000; // five days + private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); protected final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THING_THREADPOOL_NAME); - private static final long expiresIn = 432000; // five days - private static final Pattern charsetPattern = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); - private final Logger logger = LoggerFactory.getLogger(Connection.class); private final CookieManager cookieManager = new CookieManager(); @@ -152,7 +157,6 @@ public Connection(@Nullable Connection oldConnection, Gson gson) { frc = oldConnection.getFrc(); serial = oldConnection.getSerial(); deviceId = oldConnection.getDeviceId(); - } Random rand = new Random(); if (frc != null) { @@ -440,13 +444,15 @@ public boolean tryRestoreLogin(@Nullable String data, @Nullable String overloade try { String bootstrapResultJson = convertStream(connection); JsonBootstrapResult result = parseJson(bootstrapResultJson, JsonBootstrapResult.class); - Authentication authentication = result.authentication; - if (authentication != null && authentication.authenticated) { - this.customerName = authentication.customerName; - if (this.accountCustomerId == null) { - this.accountCustomerId = authentication.customerId; + if (result != null) { + Authentication authentication = result.authentication; + if (authentication != null && authentication.authenticated) { + this.customerName = authentication.customerName; + if (this.accountCustomerId == null) { + this.accountCustomerId = authentication.customerId; + } + return authentication; } - return authentication; } } catch (JsonSyntaxException | IllegalStateException e) { logger.info("No valid json received", e); @@ -471,7 +477,7 @@ public String convertStream(HttpsURLConnection connection) throws IOException { String contentType = connection.getContentType(); String charSet = null; if (contentType != null) { - Matcher m = charsetPattern.matcher(contentType); + Matcher m = CHARSET_PATTERN.matcher(contentType); if (m.find()) { charSet = m.group(1).trim().toUpperCase(); } @@ -498,7 +504,7 @@ public String makeRequestAndReturnString(String verb, String url, @Nullable Stri @Nullable Map customHeaders) throws IOException, URISyntaxException { HttpsURLConnection connection = makeRequest(verb, url, postData, json, true, customHeaders, 0); String result = convertStream(connection); - this.logger.debug("Result of {} {}:{}", verb, url, result); + logger.debug("Result of {} {}:{}", verb, url, result); return result; } @@ -507,10 +513,9 @@ public HttpsURLConnection makeRequest(String verb, String url, @Nullable String throws IOException, URISyntaxException { String currentUrl = url; int redirectCounter = 0; - while (true) // loop for handling redirect and bad request, using automatic redirect is not possible, - // because - // all response headers must be catched - { + // loop for handling redirect and bad request, using automatic redirect is not + // possible, because all response headers must be catched + while (true) { int code; HttpsURLConnection connection = null; try { @@ -693,6 +698,9 @@ public String registerConnectionAsApp(String oAutRedirectUrl) registerAppRequestJson, true, registerHeaders); JsonRegisterAppResponse registerAppResponse = parseJson(registerAppResultJson, JsonRegisterAppResponse.class); + if (registerAppResponse == null) { + throw new ConnectionException("Error: No response receivec from register application"); + } Response response = registerAppResponse.response; if (response == null) { throw new ConnectionException("Error: No response received from register application"); @@ -701,7 +709,6 @@ public String registerConnectionAsApp(String oAutRedirectUrl) if (success == null) { throw new ConnectionException("Error: No success received from register application"); } - Tokens tokens = success.tokens; if (tokens == null) { throw new ConnectionException("Error: No tokens received from register application"); @@ -710,7 +717,7 @@ public String registerConnectionAsApp(String oAutRedirectUrl) if (bearer == null) { throw new ConnectionException("Error: No bearer received from register application"); } - this.refreshToken = bearer.refresh_token; + this.refreshToken = bearer.refreshToken; if (StringUtils.isEmpty(this.refreshToken)) { throw new ConnectionException("Error: No refresh token received"); } @@ -720,6 +727,9 @@ public String registerConnectionAsApp(String oAutRedirectUrl) String usersMeResponseJson = makeRequestAndReturnString("GET", "https://alexa.amazon.com/api/users/me?platform=ios&version=2.2.223830.0", null, false, null); JsonUsersMeResponse usersMeResponse = parseJson(usersMeResponseJson, JsonUsersMeResponse.class); + if (usersMeResponse == null) { + throw new IllegalArgumentException("Received no response on me-request"); + } URI uri = new URI(usersMeResponse.marketPlaceDomainName); String host = uri.getHost(); @@ -734,9 +744,9 @@ public String registerConnectionAsApp(String oAutRedirectUrl) String deviceName = null; Extensions extensions = success.extensions; if (extensions != null) { - DeviceInfo deviceInfo = extensions.device_info; + DeviceInfo deviceInfo = extensions.deviceInfo; if (deviceInfo != null) { - deviceName = deviceInfo.device_name; + deviceName = deviceInfo.deviceName; } } if (deviceName == null) { @@ -775,10 +785,10 @@ private void exchangeToken() throws IOException, URISyntaxException { Cookie[] cookies = cookiesMap.get(domain); for (Cookie cookie : cookies) { if (cookie != null) { - HttpCookie httpCookie = new HttpCookie(cookie.Name, cookie.Value); - httpCookie.setPath(cookie.Path); + HttpCookie httpCookie = new HttpCookie(cookie.name, cookie.value); + httpCookie.setPath(cookie.path); httpCookie.setDomain(domain); - Boolean secure = cookie.Secure; + Boolean secure = cookie.secure; if (secure != null) { httpCookie.setSecure(secure); } @@ -792,7 +802,7 @@ private void exchangeToken() throws IOException, URISyntaxException { if (!verifyLogin()) { throw new ConnectionException("Verify login failed after token exchange"); } - this.renewTime = (long) (System.currentTimeMillis() + Connection.expiresIn * 1000d / 0.8d); // start renew at + this.renewTime = (long) (System.currentTimeMillis() + Connection.EXPIRES_IN * 1000d / 0.8d); // start renew at } public boolean checkRenewSession() throws UnknownHostException, URISyntaxException, IOException { @@ -876,7 +886,7 @@ public void logout() { } // parser - private T parseJson(String json, Class type) throws JsonSyntaxException, IllegalStateException { + private @Nullable T parseJson(String json, Class type) throws JsonSyntaxException, IllegalStateException { try { return gson.fromJson(json, type); } catch (JsonParseException | IllegalStateException e) { @@ -893,9 +903,11 @@ public WakeWord[] getWakeWords() { try { json = makeRequestAndReturnString(alexaServer + "/api/wake-word?cached=true"); JsonWakeWords wakeWords = parseJson(json, JsonWakeWords.class); - WakeWord[] result = wakeWords.wakeWords; - if (result != null) { - return result; + if (wakeWords != null) { + WakeWord[] result = wakeWords.wakeWords; + if (result != null) { + return result; + } } } catch (IOException | URISyntaxException e) { logger.info("getting wakewords failed", e); @@ -903,14 +915,59 @@ public WakeWord[] getWakeWords() { return new WakeWord[0]; } + public List getSmarthomeDeviceList() throws IOException, URISyntaxException { + try { + String json = makeRequestAndReturnString(alexaServer + "/api/phoenix"); + logger.debug("getSmartHomeDevices result: {}", json); + + JsonNetworkDetails networkDetails = parseJson(json, JsonNetworkDetails.class); + if (networkDetails == null) { + throw new IllegalArgumentException("received no response on network detail request"); + } + Object jsonObject = gson.fromJson(networkDetails.networkDetail, Object.class); + List result = new ArrayList<>(); + searchSmartHomeDevicesRecursive(jsonObject, result); + + return result; + } catch (Exception e) { + logger.warn("getSmartHomeDevices fails: {}", e.getMessage()); + throw e; + } + } + + private void searchSmartHomeDevicesRecursive(@Nullable Object jsonNode, List devices) { + if (jsonNode instanceof Map) { + @SuppressWarnings("rawtypes") + Map map = (Map) jsonNode; + if (map.containsKey("entityId") && map.containsKey("friendlyName") && map.containsKey("actions")) { + // device node found, create type element and add it to the results + JsonElement element = gson.toJsonTree(jsonNode); + SmartHomeDevice shd = parseJson(element.toString(), SmartHomeDevice.class); + if (shd != null) { + devices.add(shd); + } + } else if (map.containsKey("applianceGroupName")) { + JsonElement element = gson.toJsonTree(jsonNode); + SmartHomeGroup shg = parseJson(element.toString(), SmartHomeGroup.class); + if (shg != null) { + devices.add(shg); + } + } else { + map.values().forEach(value -> searchSmartHomeDevicesRecursive(value, devices)); + } + } + } + public List getDeviceList() throws IOException, URISyntaxException { String json = getDeviceListJson(); JsonDevices devices = parseJson(json, JsonDevices.class); - Device[] result = devices.devices; - if (result == null) { - return new ArrayList<>(); + if (devices != null) { + Device[] result = devices.devices; + if (result != null) { + return new ArrayList<>(Arrays.asList(result)); + } } - return new ArrayList<>(Arrays.asList(result)); + return Collections.emptyList(); } public String getDeviceListJson() throws IOException, URISyntaxException { @@ -918,14 +975,44 @@ public String getDeviceListJson() throws IOException, URISyntaxException { return json; } - public JsonPlayerState getPlayer(Device device) throws IOException, URISyntaxException { + public Map getSmartHomeDeviceStatesJson(Set applianceIds) + throws IOException, URISyntaxException { + JsonObject requestObject = new JsonObject(); + JsonArray stateRequests = new JsonArray(); + for (String applianceId : applianceIds) { + JsonObject stateRequest = new JsonObject(); + stateRequest.addProperty("entityId", applianceId); + stateRequest.addProperty("entityType", "APPLIANCE"); + stateRequests.add(stateRequest); + } + requestObject.add("stateRequests", stateRequests); + String requestBody = requestObject.toString(); + String json = makeRequestAndReturnString("POST", alexaServer + "/api/phoenix/state", requestBody, true, null); + logger.trace("Requested {} and received {}", requestBody, json); + + JsonObject responseObject = this.gson.fromJson(json, JsonObject.class); + JsonArray deviceStates = (JsonArray) responseObject.get("deviceStates"); + Map result = new HashMap<>(); + for (JsonElement deviceState : deviceStates) { + JsonObject deviceStateObject = deviceState.getAsJsonObject(); + JsonObject entity = deviceStateObject.get("entity").getAsJsonObject(); + String applicanceId = entity.get("entityId").getAsString(); + JsonElement capabilityState = deviceStateObject.get("capabilityStates"); + if (capabilityState != null && capabilityState.isJsonArray()) { + result.put(applicanceId, capabilityState.getAsJsonArray()); + } + } + return result; + } + + public @Nullable JsonPlayerState getPlayer(Device device) throws IOException, URISyntaxException { String json = makeRequestAndReturnString(alexaServer + "/api/np/player?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType + "&screenWidth=1440"); JsonPlayerState playerState = parseJson(json, JsonPlayerState.class); return playerState; } - public JsonMediaState getMediaState(Device device) throws IOException, URISyntaxException { + public @Nullable JsonMediaState getMediaState(Device device) throws IOException, URISyntaxException { String json = makeRequestAndReturnString(alexaServer + "/api/media/state?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType); JsonMediaState mediaState = parseJson(json, JsonMediaState.class); @@ -938,9 +1025,11 @@ public Activity[] getActivities(int number, @Nullable Long startTime) { json = makeRequestAndReturnString(alexaServer + "/api/activities?startTime=" + (startTime != null ? startTime : "") + "&size=" + number + "&offset=1"); JsonActivities activities = parseJson(json, JsonActivities.class); - Activity[] activiesArray = activities.activities; - if (activiesArray != null) { - return activiesArray; + if (activities != null) { + Activity[] activiesArray = activities.activities; + if (activiesArray != null) { + return activiesArray; + } } } catch (IOException | URISyntaxException e) { logger.info("getting activities failed", e); @@ -948,7 +1037,7 @@ public Activity[] getActivities(int number, @Nullable Long startTime) { return new Activity[0]; } - public JsonBluetoothStates getBluetoothConnectionStates() { + public @Nullable JsonBluetoothStates getBluetoothConnectionStates() { String json; try { json = makeRequestAndReturnString(alexaServer + "/api/bluetooth?cached=true"); @@ -960,7 +1049,7 @@ public JsonBluetoothStates getBluetoothConnectionStates() { return bluetoothStates; } - public JsonPlaylists getPlaylists(Device device) throws IOException, URISyntaxException { + public @Nullable JsonPlaylists getPlaylists(Device device) throws IOException, URISyntaxException { String json = makeRequestAndReturnString(alexaServer + "/api/cloudplayer/playlists?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType + "&mediaOwnerCustomerId=" + (StringUtils.isEmpty(this.accountCustomerId) ? device.deviceOwnerCustomerId @@ -975,6 +1064,63 @@ public void command(Device device, String command) throws IOException, URISyntax makeRequest("POST", url, command, true, true, null, 0); } + public void smartHomeCommand(String entityId, String action) throws IOException { + smartHomeCommand(entityId, action, null, null); + } + + public void smartHomeCommand(String entityId, String action, @Nullable String property, @Nullable Object value) + throws IOException { + String url = alexaServer + "/api/phoenix/state"; + + JsonObject json = new JsonObject(); + JsonArray controlRequests = new JsonArray(); + JsonObject controlRequest = new JsonObject(); + controlRequest.addProperty("entityId", entityId); + controlRequest.addProperty("entityType", "APPLIANCE"); + JsonObject parameters = new JsonObject(); + parameters.addProperty("action", action); + if (property != null) { + if (value instanceof Boolean) { + parameters.addProperty(property, (boolean) value); + } else if (value instanceof String) { + parameters.addProperty(property, (String) value); + } else if (value instanceof Number) { + parameters.addProperty(property, (Number) value); + } else if (value instanceof Character) { + parameters.addProperty(property, (Character) value); + } else if (value instanceof JsonElement) { + parameters.add(property, (JsonElement) value); + } + } + controlRequest.add("parameters", parameters); + controlRequests.add(controlRequest); + json.add("controlRequests", controlRequests); + + String requestBody = json.toString(); + try { + String resultBody = makeRequestAndReturnString("PUT", url, requestBody, true, null); + logger.debug("{}", resultBody); + JsonObject result = parseJson(resultBody, JsonObject.class); + if (result != null) { + JsonElement errors = result.get("errors"); + if (errors != null && errors.isJsonArray()) { + JsonArray errorList = errors.getAsJsonArray(); + if (errorList.size() > 0) { + logger.info("Smart home device command failed."); + logger.info("Request:"); + logger.info("{}", requestBody); + logger.info("Answer:"); + for (JsonElement error : errorList) { + logger.info("{}", error.toString()); + } + } + } + } + } catch (URISyntaxException e) { + logger.info("Wrong url {}", url, e); + } + } + public void notificationVolume(Device device, int volume) throws IOException, URISyntaxException { String url = alexaServer + "/api/device-notification-state/" + device.deviceType + "/" + device.softwareVersion + "/" + device.serialNumber; @@ -996,9 +1142,11 @@ public DeviceNotificationState[] getDeviceNotificationStates() { try { json = makeRequestAndReturnString(alexaServer + "/api/device-notification-state"); JsonDeviceNotificationState result = parseJson(json, JsonDeviceNotificationState.class); - DeviceNotificationState[] deviceNotificationStates = result.deviceNotificationStates; - if (deviceNotificationStates != null) { - return deviceNotificationStates; + if (result != null) { + DeviceNotificationState[] deviceNotificationStates = result.deviceNotificationStates; + if (deviceNotificationStates != null) { + return deviceNotificationStates; + } } } catch (IOException | URISyntaxException e) { logger.info("Error getting device notification states", e); @@ -1011,9 +1159,11 @@ public AscendingAlarmModel[] getAscendingAlarm() { try { json = makeRequestAndReturnString(alexaServer + "/api/ascending-alarm"); JsonAscendingAlarm result = parseJson(json, JsonAscendingAlarm.class); - AscendingAlarmModel[] ascendingAlarmModelList = result.ascendingAlarmModelList; - if (ascendingAlarmModelList != null) { - return ascendingAlarmModelList; + if (result != null) { + AscendingAlarmModel[] ascendingAlarmModelList = result.ascendingAlarmModelList; + if (ascendingAlarmModelList != null) { + return ascendingAlarmModelList; + } } } catch (IOException | URISyntaxException e) { logger.info("Error getting device notification states", e); @@ -1163,7 +1313,8 @@ private void executeSequenceCommandWithVolume(@Nullable Device device, String co } } - // commands: Alexa.Weather.Play, Alexa.Traffic.Play, Alexa.FlashBriefing.Play, Alexa.GoodMorning.Play, + // commands: Alexa.Weather.Play, Alexa.Traffic.Play, Alexa.FlashBriefing.Play, + // Alexa.GoodMorning.Play, // Alexa.SingASong.Play, Alexa.TellStory.Play, Alexa.Speak (textToSpeach) public void executeSequenceCommand(@Nullable Device device, String command, @Nullable Map parameters) throws IOException, URISyntaxException { @@ -1233,21 +1384,27 @@ private JsonObject createExecutionNode(@Nullable Device device, String command, public void startRoutine(Device device, String utterance) throws IOException, URISyntaxException { JsonAutomation found = null; String deviceLocale = ""; + JsonAutomation[] routines = getRoutines(); + if (routines == null) { + return; + } for (JsonAutomation routine : getRoutines()) { - Trigger[] triggers = routine.triggers; - if (triggers != null && routine.sequence != null) { - for (JsonAutomation.Trigger trigger : triggers) { - if (trigger == null) { - continue; - } - Payload payload = trigger.payload; - if (payload == null) { - continue; - } - if (StringUtils.equalsIgnoreCase(payload.utterance, utterance)) { - found = routine; - deviceLocale = payload.locale; - break; + if (routine != null) { + Trigger[] triggers = routine.triggers; + if (triggers != null && routine.sequence != null) { + for (JsonAutomation.Trigger trigger : triggers) { + if (trigger == null) { + continue; + } + Payload payload = trigger.payload; + if (payload == null) { + continue; + } + if (StringUtils.equalsIgnoreCase(payload.utterance, utterance)) { + found = routine; + deviceLocale = payload.locale; + break; + } } } } @@ -1297,7 +1454,7 @@ public void startRoutine(Device device, String utterance) throws IOException, UR } } - public JsonAutomation[] getRoutines() throws IOException, URISyntaxException { + public @Nullable JsonAutomation @Nullable [] getRoutines() throws IOException, URISyntaxException { String json = makeRequestAndReturnString(alexaServer + "/api/behaviors/automations?limit=2000"); JsonAutomation[] result = parseJson(json, JsonAutomation[].class); return result; @@ -1306,6 +1463,9 @@ public JsonAutomation[] getRoutines() throws IOException, URISyntaxException { public JsonFeed[] getEnabledFlashBriefings() throws IOException, URISyntaxException { String json = makeRequestAndReturnString(alexaServer + "/api/content-skills/enabled-feeds"); JsonEnabledFeeds result = parseJson(json, JsonEnabledFeeds.class); + if (result == null) { + return new JsonFeed[0]; + } JsonFeed[] enabledFeeds = result.enabledFeeds; if (enabledFeeds != null) { return enabledFeeds; @@ -1325,6 +1485,9 @@ public JsonNotificationSound[] getNotificationSounds(Device device) throws IOExc alexaServer + "/api/notification/sounds?deviceSerialNumber=" + device.serialNumber + "&deviceType=" + device.deviceType + "&softwareVersion=" + device.softwareVersion); JsonNotificationSounds result = parseJson(json, JsonNotificationSounds.class); + if (result == null) { + return new JsonNotificationSound[0]; + } JsonNotificationSound[] notificationSounds = result.notificationSounds; if (notificationSounds != null) { return notificationSounds; @@ -1335,6 +1498,9 @@ public JsonNotificationSound[] getNotificationSounds(Device device) throws IOExc public JsonNotificationResponse[] notifications() throws IOException, URISyntaxException { String response = makeRequestAndReturnString(alexaServer + "/api/notifications"); JsonNotificationsResponse result = parseJson(response, JsonNotificationsResponse.class); + if (result == null) { + return new JsonNotificationResponse[0]; + } JsonNotificationResponse[] notifications = result.notifications; if (notifications == null) { return new JsonNotificationResponse[0]; @@ -1342,7 +1508,7 @@ public JsonNotificationResponse[] notifications() throws IOException, URISyntaxE return notifications; } - public JsonNotificationResponse notification(Device device, String type, @Nullable String label, + public @Nullable JsonNotificationResponse notification(Device device, String type, @Nullable String label, @Nullable JsonNotificationSound sound) throws IOException, URISyntaxException { Date date = new Date(new Date().getTime()); long createdDate = date.getTime(); @@ -1374,7 +1540,7 @@ public void stopNotification(JsonNotificationResponse notification) throws IOExc makeRequestAndReturnString("DELETE", alexaServer + "/api/notifications/" + notification.id, null, true, null); } - public JsonNotificationResponse getNotificationState(JsonNotificationResponse notification) + public @Nullable JsonNotificationResponse getNotificationState(JsonNotificationResponse notification) throws IOException, URISyntaxException { String response = makeRequestAndReturnString("GET", alexaServer + "/api/notifications/" + notification.id, null, true, null); @@ -1411,22 +1577,24 @@ public void playMusicVoiceCommand(Device device, String providerId, String voice String playloadString = gson.toJson(payload); - JsonObject postValidataionJson = new JsonObject(); + JsonObject postValidationJson = new JsonObject(); - postValidataionJson.addProperty("type", "Alexa.Music.PlaySearchPhrase"); - postValidataionJson.addProperty("operationPayload", playloadString); + postValidationJson.addProperty("type", "Alexa.Music.PlaySearchPhrase"); + postValidationJson.addProperty("operationPayload", playloadString); - String postDataValidate = postValidataionJson.toString(); + String postDataValidate = postValidationJson.toString(); String validateResultJson = makeRequestAndReturnString("POST", alexaServer + "/api/behaviors/operation/validate", postDataValidate, true, null); if (StringUtils.isNotEmpty(validateResultJson)) { JsonPlayValidationResult validationResult = parseJson(validateResultJson, JsonPlayValidationResult.class); - JsonPlaySearchPhraseOperationPayload validatedOperationPayload = validationResult.operationPayload; - if (validatedOperationPayload != null) { - payload.sanitizedSearchPhrase = validatedOperationPayload.sanitizedSearchPhrase; - payload.searchPhrase = validatedOperationPayload.searchPhrase; + if (validationResult != null) { + JsonPlaySearchPhraseOperationPayload validatedOperationPayload = validationResult.operationPayload; + if (validatedOperationPayload != null) { + payload.sanitizedSearchPhrase = validatedOperationPayload.sanitizedSearchPhrase; + payload.searchPhrase = validatedOperationPayload.searchPhrase; + } } } @@ -1450,13 +1618,13 @@ public void playMusicVoiceCommand(Device device, String providerId, String voice makeRequest("POST", alexaServer + "/api/behaviors/preview", postData, true, true, null, 3); } - public JsonEqualizer getEqualizer(Device device) throws IOException, URISyntaxException { + public @Nullable JsonEqualizer getEqualizer(Device device) throws IOException, URISyntaxException { String json = makeRequestAndReturnString( alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType); return parseJson(json, JsonEqualizer.class); } - public void SetEqualizer(Device device, JsonEqualizer settings) throws IOException, URISyntaxException { + public void setEqualizer(Device device, JsonEqualizer settings) throws IOException, URISyntaxException { String postData = gson.toJson(settings); makeRequest("POST", alexaServer + "/api/equalizer/" + device.serialNumber + "/" + device.deviceType, postData, true, true, null, 0); diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java index f99797e10d609..4d6c9e5b05f63 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/WebSocketConnection.java @@ -158,7 +158,7 @@ public void close() { } logger.trace("Connect future = {}", sessionFuture); final Future sessionFuture = this.sessionFuture; - if (!sessionFuture.isDone()) { + if (sessionFuture != null && !sessionFuture.isDone()) { sessionFuture.cancel(true); } try { @@ -587,9 +587,7 @@ byte[] encodePing() { 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; - for (int q = 0; q < header.length; q++) { - bufferPing[q] = header[q]; - } + System.arraycopy(header, 0, bufferPing, 0, header.length); idx += header.length; encode(bufferPing, 0, idx, 4); idx += 4; diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java index 1c947956975b4..f64dc05a691bb 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandler.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.net.URISyntaxException; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.types.Command; import org.openhab.binding.amazonechocontrol.internal.Connection; @@ -30,6 +31,7 @@ * * @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; diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java index 89af22d404a7f..8b59e4f07b04c 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerAnnouncement.java @@ -16,6 +16,7 @@ import java.net.URISyntaxException; import org.apache.commons.lang.StringEscapeUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; @@ -30,6 +31,7 @@ * * @author Michael Geramb - Initial contribution */ +@NonNullByDefault public class ChannelHandlerAnnouncement extends ChannelHandler { private static final String CHANNEL_NAME = "announcement"; @@ -89,16 +91,16 @@ public boolean tryHandleCommand(Device device, Connection connection, String cha } thingHandler.startAnnouncment(device, speak, body, title, volume); } - RefreshChannel(); + refreshChannel(); } return false; } - void RefreshChannel() { + private void refreshChannel() { thingHandler.updateChannelState(CHANNEL_NAME, new StringType("")); } - static class AnnouncementRequestJson { + private static class AnnouncementRequestJson { public @Nullable Boolean sound; public @Nullable String title; public @Nullable String body; diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java index 5d1be525be3bd..b8be3997c9c80 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/channelhandler/ChannelHandlerSendMessage.java @@ -16,6 +16,7 @@ import java.net.URISyntaxException; import java.time.LocalDateTime; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; @@ -29,6 +30,7 @@ * * @author Michael Geramb - Initial contribution */ +@NonNullByDefault public class ChannelHandlerSendMessage extends ChannelHandler { private static final String CHANNEL_NAME = "sendMessage"; private @Nullable AccountJson accountJson; @@ -49,7 +51,7 @@ public boolean tryHandleCommand(Device device, Connection connection, String cha AccountJson currentAccountJson = this.accountJson; if (currentAccountJson == null) { String accountResult = connection.makeRequestAndReturnString(baseUrl + "/accounts"); - AccountJson[] accountsJson = this.gson.fromJson(accountResult, AccountJson[].class); + AccountJson @Nullable [] accountsJson = gson.fromJson(accountResult, AccountJson[].class); if (accountsJson == null) { return false; } @@ -85,16 +87,17 @@ public boolean tryHandleCommand(Device device, Connection connection, String cha + "/messages"; connection.makeRequestAndReturnString("POST", sendUrl, sendConversationBody, true, null); } - RefreshChannel(); + refreshChannel(); } return false; } - void RefreshChannel() { + private void refreshChannel() { thingHandler.updateChannelState(CHANNEL_NAME, new StringType("")); } - static class AccountJson { + @SuppressWarnings("unused") + private static class AccountJson { public @Nullable String commsId; public @Nullable String directedId; public @Nullable String phoneCountryCode; @@ -110,7 +113,8 @@ static class AccountJson { public @Nullable Boolean speakerProvisioned; } - static class SendConversationJson { + @SuppressWarnings("unused") + private static class SendConversationJson { public @Nullable String conversationId; public @Nullable String clientMessageId; public @Nullable Integer messageId; @@ -120,7 +124,7 @@ static class SendConversationJson { public Payload payload = new Payload(); public Integer status = 1; - static class Payload { + private static class Payload { public @Nullable String text; } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java index 1ba4d01d21429..da91ce1dbe351 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/AmazonEchoDiscovery.java @@ -12,7 +12,15 @@ */ package org.openhab.binding.amazonechocontrol.internal.discovery; -import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.*; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_FAMILY; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_SERIAL_NUMBER; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.SUPPORTED_ECHO_THING_TYPES_UIDS; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_ECHO; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_ECHO_SHOW; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_ECHO_SPOT; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_ECHO_WHA; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_FLASH_BRIEFING_PROFILE; import java.util.Date; import java.util.HashMap; @@ -28,6 +36,8 @@ import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; import org.eclipse.smarthome.config.discovery.DiscoveryResult; import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.config.discovery.DiscoveryServiceCallback; +import org.eclipse.smarthome.config.discovery.ExtendedDiscoveryService; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.ThingUID; import org.openhab.binding.amazonechocontrol.internal.Connection; @@ -44,17 +54,24 @@ * @author Michael Geramb - Initial contribution */ @NonNullByDefault -public class AmazonEchoDiscovery extends AbstractDiscoveryService { +public class AmazonEchoDiscovery extends AbstractDiscoveryService implements ExtendedDiscoveryService { - private final AccountHandler accountHandler; + AccountHandler accountHandler; private final Logger logger = LoggerFactory.getLogger(AmazonEchoDiscovery.class); private final Set discoverdFlashBriefings = new HashSet<>(); + private @Nullable DiscoveryServiceCallback discoveryServiceCallback; + private @Nullable ScheduledFuture startScanStateJob; - private long activateTimeStamp; + private @Nullable Long activateTimeStamp; + + @Override + public void setDiscoveryServiceCallback(DiscoveryServiceCallback discoveryServiceCallback) { + this.discoveryServiceCallback = discoveryServiceCallback; + } public AmazonEchoDiscovery(AccountHandler accountHandler) { - super(SUPPORTED_THING_TYPES_UIDS, 10); + super(SUPPORTED_ECHO_THING_TYPES_UIDS, 10); this.accountHandler = accountHandler; } @@ -70,8 +87,10 @@ public void deactivate() { @Override protected void startScan() { stopScanJob(); - removeOlderResults(activateTimeStamp); - + final Long activateTimeStamp = this.activateTimeStamp; + if (activateTimeStamp != null) { + removeOlderResults(activateTimeStamp); + } setDevices(accountHandler.updateDeviceList()); String currentFlashBriefingConfiguration = accountHandler.getNewCurrentFlashbriefingConfiguration(); @@ -109,7 +128,7 @@ protected void stopBackgroundDiscovery() { stopScanJob(); } - private void stopScanJob() { + void stopScanJob() { @Nullable ScheduledFuture currentStartScanStateJob = startScanStateJob; if (currentStartScanStateJob != null) { @@ -125,10 +144,16 @@ public void activate(@Nullable Map config) { if (config != null) { modified(config); } - activateTimeStamp = new Date().getTime(); + if (activateTimeStamp == null) { + activateTimeStamp = new Date().getTime(); + } } synchronized void setDevices(List deviceList) { + DiscoveryServiceCallback discoveryServiceCallback = this.discoveryServiceCallback; + if (discoveryServiceCallback == null) { + return; + } for (Device device : deviceList) { String serialNumber = device.serialNumber; if (serialNumber != null) { @@ -150,7 +175,12 @@ synchronized void setDevices(List deviceList) { ThingUID brigdeThingUID = this.accountHandler.getThing().getUID(); ThingUID thingUID = new ThingUID(thingTypeId, brigdeThingUID, serialNumber); - + if (discoveryServiceCallback.getExistingDiscoveryResult(thingUID) != null) { + continue; + } + if (discoveryServiceCallback.getExistingThing(thingUID) != null) { + continue; + } DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(device.accountName) .withProperty(DEVICE_PROPERTY_SERIAL_NUMBER, serialNumber) .withProperty(DEVICE_PROPERTY_FAMILY, deviceFamily) @@ -170,13 +200,30 @@ public synchronized void discoverFlashBriefingProfiles(String currentFlashBriefi if (currentFlashBriefingJson.isEmpty()) { return; } + DiscoveryServiceCallback discoveryServiceCallback = this.discoveryServiceCallback; + if (discoveryServiceCallback == null) { + return; + } if (!discoverdFlashBriefings.contains(currentFlashBriefingJson)) { - ThingUID brigdeThingUID = this.accountHandler.getThing().getUID(); - ThingUID thingUID = new ThingUID(THING_TYPE_FLASH_BRIEFING_PROFILE, brigdeThingUID, - Integer.toString(currentFlashBriefingJson.hashCode())); - - DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel("FlashBriefing") + ThingUID freeThingUID = null; + int freeIndex = 0; + for (int i = 1; i < 1000; i++) { + String id = Integer.toString(i); + ThingUID brigdeThingUID = this.accountHandler.getThing().getUID(); + ThingUID thingUID = new ThingUID(THING_TYPE_FLASH_BRIEFING_PROFILE, brigdeThingUID, id); + if (discoveryServiceCallback.getExistingThing(thingUID) == null + && discoveryServiceCallback.getExistingDiscoveryResult(thingUID) == null) { + freeThingUID = thingUID; + freeIndex = i; + break; + } + } + if (freeThingUID == null) { + logger.debug("No more free flashbriefing thing ID found"); + return; + } + DiscoveryResult result = DiscoveryResultBuilder.create(freeThingUID).withLabel("FlashBriefing " + freeIndex) .withProperty(DEVICE_PROPERTY_FLASH_BRIEFING_PROFILE, currentFlashBriefingJson) .withBridge(accountHandler.getThing().getUID()).build(); logger.debug("Flash Briefing {} discovered", currentFlashBriefingJson); diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java new file mode 100644 index 0000000000000..ba48397429fea --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/discovery/SmartHomeDevicesDiscovery.java @@ -0,0 +1,247 @@ +/** + * Copyright (c) 2010-2020 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.discovery; + +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_ID; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.SUPPORTED_SMART_HOME_THING_TYPES_UIDS; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_SMART_HOME_DEVICE; +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.THING_TYPE_SMART_HOME_DEVICE_GROUP; + +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResult; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.config.discovery.DiscoveryServiceCallback; +import org.eclipse.smarthome.config.discovery.ExtendedDiscoveryService; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.handler.AccountHandler; +import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; +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.osgi.service.component.annotations.Activate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class SmartHomeDevicesDiscovery extends AbstractDiscoveryService implements ExtendedDiscoveryService { + private AccountHandler accountHandler; + private final Logger logger = LoggerFactory.getLogger(SmartHomeDevicesDiscovery.class); + + private @Nullable ScheduledFuture startScanStateJob; + private @Nullable Long activateTimeStamp; + + private @Nullable DiscoveryServiceCallback discoveryServiceCallback; + + @Override + public void setDiscoveryServiceCallback(DiscoveryServiceCallback discoveryServiceCallback) { + this.discoveryServiceCallback = discoveryServiceCallback; + } + + 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(); + } + + @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(); + } + + @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; + } + super.stopScan(); + } + + @Override + @Activate + public void activate(@Nullable Map config) { + super.activate(config); + if (config != null) { + modified(config); + } + Long activateTimeStamp = this.activateTimeStamp; + if (activateTimeStamp == null) { + this.activateTimeStamp = new Date().getTime(); + } + }; + + synchronized void setSmartHomeDevices(List deviceList) { + DiscoveryServiceCallback discoveryServiceCallback = this.discoveryServiceCallback; + + if (discoveryServiceCallback == null) { + return; + } + int smartHomeDeviceDiscoveryMode = accountHandler.getSmartHomeDevicesDiscoveryMode(); + if (smartHomeDeviceDiscoveryMode == 0) { + return; + } + + for (Object smartHomeDevice : deviceList) { + ThingUID bridgeThingUID = this.accountHandler.getThing().getUID(); + ThingUID thingUID = null; + String deviceName = null; + Map props = new HashMap<>(); + + if (smartHomeDevice instanceof SmartHomeDevice) { + SmartHomeDevice shd = (SmartHomeDevice) smartHomeDevice; + + String entityId = shd.entityId; + if (entityId == null) { + // No entity id + continue; + } + String id = shd.findId(); + if (id == null) { + // No id + continue; + } + boolean isSkillDevice = false; + DriverIdentity driverIdentity = shd.driverIdentity; + isSkillDevice = driverIdentity != null && "SKILL".equals(driverIdentity.namespace); + + if (smartHomeDeviceDiscoveryMode == 1 && isSkillDevice) { + // Connected through skill + continue; + } + if (!(smartHomeDeviceDiscoveryMode == 2) && "openHAB".equalsIgnoreCase(shd.manufacturerName)) { + // OpenHAB device + continue; + } + + if (Stream.of(shd.capabilities).noneMatch(capability -> capability != null + && Constants.SUPPORTED_INTERFACES.contains(capability.interfaceName))) { + // No supported interface found + continue; + } + + thingUID = new ThingUID(THING_TYPE_SMART_HOME_DEVICE, bridgeThingUID, entityId.replace(".", "-")); + + if ("Amazon".equals(shd.manufacturerName) && driverIdentity != null + && "SonarCloudService".equals(driverIdentity.identifier)) { + deviceName = "Alexa Guard on " + shd.friendlyName; + } else if ("Amazon".equals(shd.manufacturerName) && driverIdentity != null + && "OnGuardSmartHomeBridgeService".equals(driverIdentity.identifier)) { + deviceName = "Alexa Guard"; + } else if (shd.aliases != null && shd.aliases.length > 0 && shd.aliases[0] != null + && shd.aliases[0].friendlyName != null) { + deviceName = shd.aliases[0].friendlyName; + } else { + deviceName = shd.friendlyName; + } + props.put(DEVICE_PROPERTY_ID, id); + } + + if (smartHomeDevice instanceof SmartHomeGroup) { + SmartHomeGroup shg = (SmartHomeGroup) smartHomeDevice; + String id = shg.findId(); + if (id == null) { + // No id + continue; + } + Set supportedChildren = SmartHomeDeviceHandler.getSupportedSmartHomeDevices(shg, + deviceList); + if (supportedChildren.size() == 0) { + // No children with an supported interface + continue; + } + thingUID = new ThingUID(THING_TYPE_SMART_HOME_DEVICE_GROUP, bridgeThingUID, id.replace(".", "-")); + deviceName = shg.applianceGroupName; + props.put(DEVICE_PROPERTY_ID, id); + } + + if (thingUID != null) { + if (discoveryServiceCallback.getExistingDiscoveryResult(thingUID) != null) { + continue; + } + + if (discoveryServiceCallback.getExistingThing(thingUID) != null) { + continue; + } + + DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel(deviceName) + .withProperties(props).withBridge(bridgeThingUID).build(); + + logger.debug("Device [{}] found.", deviceName); + + thingDiscovered(result); + } + } + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java index b7ef5d9207156..ee9f443306c06 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/AccountHandler.java @@ -18,11 +18,14 @@ import java.net.UnknownHostException; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -41,6 +44,7 @@ import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.amazonechocontrol.internal.AccountHandlerConfig; import org.openhab.binding.amazonechocontrol.internal.AccountServlet; import org.openhab.binding.amazonechocontrol.internal.Connection; import org.openhab.binding.amazonechocontrol.internal.ConnectionException; @@ -68,12 +72,16 @@ import org.openhab.binding.amazonechocontrol.internal.jsons.JsonNotificationSound; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPlaylists; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonPushCommand; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; import org.openhab.binding.amazonechocontrol.internal.jsons.JsonWakeWords.WakeWord; +import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; +import org.openhab.binding.amazonechocontrol.internal.smarthome.SmartHomeDeviceStateGroupUpdateCalculator; import org.osgi.service.http.HttpService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonSyntaxException; /** @@ -83,26 +91,38 @@ */ @NonNullByDefault public class AccountHandler extends BaseBridgeHandler implements IWebSocketCommandHandler, IAmazonThingHandler { - private final Logger logger = LoggerFactory.getLogger(AccountHandler.class); private Storage stateStorage; private @Nullable Connection connection; private @Nullable WebSocketConnection webSocketConnection; - private final Set echoHandlers = new HashSet<>(); - private final Set flashBriefingProfileHandlers = new HashSet<>(); + + private final Set echoHandlers = new CopyOnWriteArraySet<>(); + private final Set smartHomeDeviceHandlers = new CopyOnWriteArraySet<>(); + private final Set flashBriefingProfileHandlers = new CopyOnWriteArraySet<>(); + private final Object synchronizeConnection = new Object(); private Map jsonSerialNumberDeviceMapping = new HashMap<>(); + private Map jsonIdSmartHomeDeviceMapping = new HashMap<>(); + private Map jsonSerialNumberSmartHomeDeviceMapping = new HashMap<>(); + private @Nullable ScheduledFuture checkDataJob; private @Nullable ScheduledFuture checkLoginJob; + private @Nullable ScheduledFuture updateSmartHomeStateJob; private @Nullable ScheduledFuture refreshAfterCommandJob; - private @Nullable ScheduledFuture foceCheckDataJob; + private @Nullable ScheduledFuture refreshSmartHomeAfterCommandJob; + private final Object synchronizeSmartHomeJobScheduler = new Object(); + private @Nullable ScheduledFuture forceCheckDataJob; private String currentFlashBriefingJson = ""; private final HttpService httpService; private @Nullable AccountServlet accountServlet; private final Gson gson; - int checkDataCounter; + private int checkDataCounter; + private final LinkedBlockingQueue requestedDeviceUpdates = new LinkedBlockingQueue<>(); + private @Nullable SmartHomeDeviceStateGroupUpdateCalculator smartHomeDeviceStateGroupUpdateCalculator; private List channelHandlers = new ArrayList<>(); + private AccountHandlerConfig handlerConfig = new AccountHandlerConfig(); + public AccountHandler(Bridge bridge, HttpService httpService, Storage stateStorage, Gson gson) { super(bridge); this.gson = gson; @@ -113,7 +133,7 @@ public AccountHandler(Bridge bridge, HttpService httpService, Storage st @Override public void initialize() { - logger.debug("amazon account bridge starting..."); + handlerConfig = getConfig().as(AccountHandlerConfig.class); synchronized (synchronizeConnection) { Connection connection = this.connection; @@ -121,8 +141,13 @@ public void initialize() { this.connection = new Connection(null, gson); } } - if (this.accountServlet == null) { - this.accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this, gson); + + if (accountServlet == null) { + try { + accountServlet = new AccountServlet(httpService, this.getThing().getUID().getId(), this, gson); + } catch (IllegalStateException e) { + logger.warn("Failed to create account servlet", e); + } } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "Wait for login"); @@ -130,7 +155,18 @@ public void initialize() { checkLoginJob = scheduler.scheduleWithFixedDelay(this::checkLogin, 0, 60, TimeUnit.SECONDS); checkDataJob = scheduler.scheduleWithFixedDelay(this::checkData, 4, 60, TimeUnit.SECONDS); - logger.debug("amazon account bridge handler started."); + int pollingIntervalAlexa = handlerConfig.pollingIntervalSmartHomeAlexa; + if (pollingIntervalAlexa < 10) { + pollingIntervalAlexa = 10; + } + int pollingIntervalSkills = handlerConfig.pollingIntervalSmartSkills; + if (pollingIntervalSkills < 60) { + pollingIntervalSkills = 60; + } + smartHomeDeviceStateGroupUpdateCalculator = new SmartHomeDeviceStateGroupUpdateCalculator(pollingIntervalAlexa, + pollingIntervalSkills); + updateSmartHomeStateJob = scheduler.scheduleWithFixedDelay(() -> updateSmartHomeState(null), 20, 10, + TimeUnit.SECONDS); } @Override @@ -162,30 +198,34 @@ public void handleCommand(ChannelUID channelUID, Command command) { } public List getFlashBriefingProfileHandlers() { - return new ArrayList<>(this.flashBriefingProfileHandlers); + return new ArrayList<>(flashBriefingProfileHandlers); } public List getLastKnownDevices() { return new ArrayList<>(jsonSerialNumberDeviceMapping.values()); } + public List getLastKnownSmartHomeDevices() { + return new ArrayList<>(jsonIdSmartHomeDeviceMapping.values()); + } + public void addEchoHandler(EchoHandler echoHandler) { - synchronized (echoHandlers) { - if (!echoHandlers.add(echoHandler)) { - return; - } + if (echoHandlers.add(echoHandler)) { + + forceCheckData(); } - forceCheckData(); } - public void forceCheckData() { - if (foceCheckDataJob == null) { - foceCheckDataJob = scheduler.schedule(this::forceCheckDataHandler, 1000, TimeUnit.MILLISECONDS); + public void addSmartHomeDeviceHandler(SmartHomeDeviceHandler smartHomeDeviceHandler) { + if (smartHomeDeviceHandlers.add(smartHomeDeviceHandler)) { + forceCheckData(); } } - void forceCheckDataHandler() { - this.checkData(); + public void forceCheckData() { + if (forceCheckDataJob == null) { + forceCheckDataJob = scheduler.schedule(this::checkData, 1000, TimeUnit.MILLISECONDS); + } } public @Nullable Thing findThingBySerialNumber(@Nullable String deviceSerialNumber) { @@ -197,20 +237,16 @@ void forceCheckDataHandler() { } public @Nullable EchoHandler findEchoHandlerBySerialNumber(@Nullable String deviceSerialNumber) { - synchronized (echoHandlers) { - for (EchoHandler echoHandler : echoHandlers) { - if (StringUtils.equals(echoHandler.findSerialNumber(), deviceSerialNumber)) { - return echoHandler; - } + for (EchoHandler echoHandler : echoHandlers) { + if (deviceSerialNumber != null && deviceSerialNumber.equals(echoHandler.findSerialNumber())) { + return echoHandler; } } return null; } public void addFlashBriefingProfileHandler(FlashBriefingProfileHandler flashBriefingProfileHandler) { - synchronized (flashBriefingProfileHandlers) { - flashBriefingProfileHandlers.add(flashBriefingProfileHandler); - } + flashBriefingProfileHandlers.add(flashBriefingProfileHandler); Connection connection = this.connection; if (connection != null && connection.getIsLoggedIn()) { if (currentFlashBriefingJson.isEmpty()) { @@ -240,15 +276,15 @@ public void handleRemoval() { public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { // check for echo handler if (childHandler instanceof EchoHandler) { - synchronized (echoHandlers) { - echoHandlers.remove(childHandler); - } + echoHandlers.remove(childHandler); } // check for flash briefing profile handler if (childHandler instanceof FlashBriefingProfileHandler) { - synchronized (flashBriefingProfileHandlers) { - flashBriefingProfileHandlers.remove(childHandler); - } + flashBriefingProfileHandlers.remove(childHandler); + } + // check for flash briefing profile handler + if (childHandler instanceof SmartHomeDeviceHandler) { + smartHomeDeviceHandlers.remove(childHandler); } super.childHandlerDisposed(childHandler, childThing); } @@ -266,30 +302,36 @@ public void dispose() { private void cleanup() { logger.debug("cleanup {}", getThing().getUID().getAsString()); - @Nullable + ScheduledFuture updateSmartHomeStateJob = this.updateSmartHomeStateJob; + if (updateSmartHomeStateJob != null) { + updateSmartHomeStateJob.cancel(true); + this.updateSmartHomeStateJob = null; + } ScheduledFuture refreshJob = this.checkDataJob; if (refreshJob != null) { refreshJob.cancel(true); this.checkDataJob = null; } - @Nullable ScheduledFuture refreshLogin = this.checkLoginJob; if (refreshLogin != null) { refreshLogin.cancel(true); this.checkLoginJob = null; } - @Nullable - ScheduledFuture foceCheckDataJob = this.foceCheckDataJob; + ScheduledFuture foceCheckDataJob = this.forceCheckDataJob; if (foceCheckDataJob != null) { foceCheckDataJob.cancel(true); - this.foceCheckDataJob = null; + this.forceCheckDataJob = null; } - @Nullable - ScheduledFuture refreshDataDelayed = this.refreshAfterCommandJob; - if (refreshDataDelayed != null) { - refreshDataDelayed.cancel(true); + ScheduledFuture refreshAfterCommandJob = this.refreshAfterCommandJob; + if (refreshAfterCommandJob != null) { + refreshAfterCommandJob.cancel(true); this.refreshAfterCommandJob = null; } + ScheduledFuture refreshSmartHomeAfterCommandJob = this.refreshSmartHomeAfterCommandJob; + if (refreshSmartHomeAfterCommandJob != null) { + refreshSmartHomeAfterCommandJob.cancel(true); + this.refreshSmartHomeAfterCommandJob = null; + } Connection connection = this.connection; if (connection != null) { connection.logout(); @@ -341,7 +383,6 @@ private void checkLogin() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); } } - } catch (Exception e) { // this handler can be removed later, if we know that nothing else can fail. logger.error("check login fails with unexpected error", e); } @@ -360,6 +401,7 @@ public void setConnection(@Nullable Connection connection) { closeWebSocketConnection(); if (connection != null) { updateDeviceList(); + updateSmartHomeDeviceList(false); updateFlashBriefingHandlers(); updateStatus(ThingStatus.ONLINE); scheduleUpdate(); @@ -375,7 +417,7 @@ void closeWebSocketConnection() { } } - boolean checkWebSocketConnection() { + private boolean checkWebSocketConnection() { WebSocketConnection webSocketConnection = this.webSocketConnection; if (webSocketConnection == null || webSocketConnection.isClosed()) { Connection connection = this.connection; @@ -398,9 +440,9 @@ private void checkData() { Connection connection = this.connection; if (connection != null && connection.getIsLoggedIn()) { checkDataCounter++; - if (checkDataCounter > 60 || foceCheckDataJob != null) { + if (checkDataCounter > 60 || forceCheckDataJob != null) { checkDataCounter = 0; - foceCheckDataJob = null; + forceCheckDataJob = null; } if (!checkWebSocketConnection() || checkDataCounter == 0) { refreshData(); @@ -433,9 +475,8 @@ private void refreshNotifications(@Nullable JsonCommandPayloadPushNotificationCh } ZonedDateTime timeStampNow = ZonedDateTime.now(); - for (EchoHandler child : echoHandlers) { - child.updateNotifications(timeStamp, timeStampNow, pushPayload, notifications); - } + echoHandlers.forEach( + echoHandler -> echoHandler.updateNotifications(timeStamp, timeStampNow, pushPayload, notifications)); } private void refreshData() { @@ -457,6 +498,7 @@ private void refreshData() { // get all devices registered in the account updateDeviceList(); + updateSmartHomeDeviceList(false); updateFlashBriefingHandlers(); DeviceNotificationState[] deviceNotificationStates = null; @@ -484,9 +526,8 @@ private void refreshData() { } // forward device information to echo handler for (EchoHandler child : echoHandlers) { - Device device = findDeviceJson(child); + Device device = findDeviceJson(child.findSerialNumber()); - @Nullable JsonNotificationSound[] notificationSounds = null; JsonPlaylists playlists = null; if (device != null && currentConnection.getIsLoggedIn()) { @@ -548,11 +589,6 @@ private void refreshData() { } } - public @Nullable Device findDeviceJson(EchoHandler echoHandler) { - String serialNumber = echoHandler.findSerialNumber(); - return findDeviceJson(serialNumber); - } - public @Nullable Device findDeviceJson(@Nullable String serialNumber) { Device result = null; if (StringUtils.isNotEmpty(serialNumber)) { @@ -594,6 +630,7 @@ public List updateDeviceList() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); } if (devices != null) { + // create new device map Map newJsonSerialDeviceMapping = new HashMap<>(); for (Device device : devices) { String serialNumber = device.serialNumber; @@ -604,27 +641,27 @@ public List updateDeviceList() { } jsonSerialNumberDeviceMapping = newJsonSerialDeviceMapping; } - WakeWord[] wakeWords = currentConnection.getWakeWords(); - synchronized (echoHandlers) { - for (EchoHandler child : echoHandlers) { - String serialNumber = child.findSerialNumber(); - String deviceWakeWord = null; - for (WakeWord wakeWord : wakeWords) { - if (wakeWord != null) { - if (StringUtils.equals(wakeWord.deviceSerialNumber, serialNumber)) { - deviceWakeWord = wakeWord.wakeWord; - break; - } + WakeWord[] wakeWords = currentConnection.getWakeWords(); + // update handlers + for (EchoHandler echoHandler : echoHandlers) { + String serialNumber = echoHandler.findSerialNumber(); + String deviceWakeWord = null; + for (WakeWord wakeWord : wakeWords) { + if (wakeWord != null) { + if (serialNumber != null && serialNumber.equals(wakeWord.deviceSerialNumber)) { + deviceWakeWord = wakeWord.wakeWord; + break; } } - child.setDeviceAndUpdateThingState(this, findDeviceJson(child), deviceWakeWord); } + echoHandler.setDeviceAndUpdateThingState(this, findDeviceJson(serialNumber), deviceWakeWord); } + if (devices != null) { return devices; } - return new ArrayList<>(); + return Collections.emptyList(); } public void setEnabledFlashBriefingsJson(String flashBriefingJson) { @@ -653,19 +690,17 @@ public String updateFlashBriefingHandlers() { } private String updateFlashBriefingHandlers(Connection currentConnection) { - synchronized (flashBriefingProfileHandlers) { - if (!flashBriefingProfileHandlers.isEmpty() || currentFlashBriefingJson.isEmpty()) { - updateFlashBriefingProfiles(currentConnection); - } - boolean flashBriefingProfileFound = false; - for (FlashBriefingProfileHandler child : flashBriefingProfileHandlers) { - flashBriefingProfileFound |= child.initialize(this, currentFlashBriefingJson); - } - if (flashBriefingProfileFound) { - return ""; - } - return this.currentFlashBriefingJson; + if (!flashBriefingProfileHandlers.isEmpty() || currentFlashBriefingJson.isEmpty()) { + updateFlashBriefingProfiles(currentConnection); } + boolean flashBriefingProfileFound = false; + for (FlashBriefingProfileHandler child : flashBriefingProfileHandlers) { + flashBriefingProfileFound |= child.initialize(this, currentFlashBriefingJson); + } + if (flashBriefingProfileFound) { + return ""; + } + return this.currentFlashBriefingJson; } public @Nullable Connection findConnection() { @@ -751,7 +786,6 @@ void handleWebsocketCommand(JsonPushCommand pushCommand) { } private void handlePushDeviceCommand(DopplerId dopplerId, String command, String payload) { - @Nullable EchoHandler echoHandler = findEchoHandlerBySerialNumber(dopplerId.deviceSerialNumber); if (echoHandler != null) { echoHandler.handlePushCommand(command, payload); @@ -800,4 +834,163 @@ private void handlePushActivity(@Nullable String payload) { void refreshAfterCommand() { refreshData(); } + + private @Nullable SmartHomeBaseDevice findSmartDeviceHomeJson(SmartHomeDeviceHandler handler) { + String id = handler.getId(); + if (!id.isEmpty()) { + return jsonIdSmartHomeDeviceMapping.get(id); + } + return null; + } + + public int getSmartHomeDevicesDiscoveryMode() { + return handlerConfig.discoverSmartHome; + } + + public List updateSmartHomeDeviceList(boolean forceUpdate) { + Connection currentConnection = connection; + if (currentConnection == null) { + return Collections.emptyList(); + } + + if (!forceUpdate && smartHomeDeviceHandlers.isEmpty() && getSmartHomeDevicesDiscoveryMode() == 0) { + return Collections.emptyList(); + } + + List smartHomeDevices = null; + try { + if (currentConnection.getIsLoggedIn()) { + smartHomeDevices = currentConnection.getSmarthomeDeviceList(); + } + } catch (IOException | URISyntaxException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); + } + if (smartHomeDevices != null) { + // create new id map + Map newJsonIdSmartHomeDeviceMapping = new HashMap<>(); + for (Object smartHomeDevice : smartHomeDevices) { + if (smartHomeDevice instanceof SmartHomeBaseDevice) { + SmartHomeBaseDevice smartHomeBaseDevice = (SmartHomeBaseDevice) smartHomeDevice; + String id = smartHomeBaseDevice.findId(); + if (id != null) { + newJsonIdSmartHomeDeviceMapping.put(id, smartHomeBaseDevice); + } + } + } + jsonIdSmartHomeDeviceMapping = newJsonIdSmartHomeDeviceMapping; + } + // update handlers + smartHomeDeviceHandlers + .forEach(child -> child.setDeviceAndUpdateThingState(this, findSmartDeviceHomeJson(child))); + + if (smartHomeDevices != null) { + Map newJsonSerialNumberSmartHomeDeviceMapping = new HashMap<>(); + for (Object smartDevice : smartHomeDevices) { + if (smartDevice instanceof SmartHomeDevice) { + SmartHomeDevice shd = (SmartHomeDevice) smartDevice; + String entityId = shd.entityId; + if (entityId != null) { + newJsonSerialNumberSmartHomeDeviceMapping.put(entityId, shd); + } + } + } + jsonSerialNumberSmartHomeDeviceMapping = newJsonSerialNumberSmartHomeDeviceMapping; + } + if (smartHomeDevices != null) { + return smartHomeDevices; + } + + return Collections.emptyList(); + } + + public void forceDelayedSmartHomeStateUpdate(@Nullable String deviceId) { + if (deviceId == null) { + return; + } + synchronized (synchronizeSmartHomeJobScheduler) { + requestedDeviceUpdates.add(deviceId); + ScheduledFuture refreshSmartHomeAfterCommandJob = this.refreshSmartHomeAfterCommandJob; + if (refreshSmartHomeAfterCommandJob != null) { + refreshSmartHomeAfterCommandJob.cancel(false); + } + this.refreshSmartHomeAfterCommandJob = scheduler.schedule(this::updateSmartHomeStateJob, 500, + TimeUnit.MILLISECONDS); + } + } + + private void updateSmartHomeStateJob() { + Set deviceUpdates = new HashSet<>(); + + synchronized (synchronizeSmartHomeJobScheduler) { + Connection connection = this.connection; + if (connection == null || !connection.getIsLoggedIn()) { + this.refreshSmartHomeAfterCommandJob = scheduler.schedule(this::updateSmartHomeStateJob, 1000, + TimeUnit.MILLISECONDS); + return; + } + requestedDeviceUpdates.drainTo(deviceUpdates); + this.refreshSmartHomeAfterCommandJob = null; + } + + deviceUpdates.forEach(this::updateSmartHomeState); + } + + private synchronized void updateSmartHomeState(@Nullable String deviceFilterId) { + try { + logger.debug("updateSmartHomeState started"); + Connection connection = this.connection; + if (connection == null || !connection.getIsLoggedIn()) { + return; + } + List allDevices = getLastKnownSmartHomeDevices(); + Set applianceIds = new HashSet<>(); + if (deviceFilterId != null) { + applianceIds.add(deviceFilterId); + } else { + SmartHomeDeviceStateGroupUpdateCalculator smartHomeDeviceStateGroupUpdateCalculator = this.smartHomeDeviceStateGroupUpdateCalculator; + if (smartHomeDeviceStateGroupUpdateCalculator == null) { + return; + } + if (smartHomeDeviceHandlers.isEmpty()) { + return; + } + List devicesToUpdate = new ArrayList<>(); + for (SmartHomeDeviceHandler device : smartHomeDeviceHandlers) { + String id = device.getId(); + SmartHomeBaseDevice baseDevice = jsonIdSmartHomeDeviceMapping.get(id); + SmartHomeDeviceHandler.getSupportedSmartHomeDevices(baseDevice, allDevices) + .forEach(devicesToUpdate::add); + } + smartHomeDeviceStateGroupUpdateCalculator.removeDevicesWithNoUpdate(devicesToUpdate); + devicesToUpdate.stream().map(shd -> shd.applianceId).forEach(applianceId -> { + if (applianceId != null) { + applianceIds.add(applianceId); + } + }); + if (applianceIds.isEmpty()) { + return; + } + + } + Map applianceIdToCapabilityStates = connection + .getSmartHomeDeviceStatesJson(applianceIds); + + for (SmartHomeDeviceHandler smartHomeDeviceHandler : smartHomeDeviceHandlers) { + String id = smartHomeDeviceHandler.getId(); + if (requestedDeviceUpdates.contains(id)) { + logger.debug("Device update {} suspended", id); + continue; + } + if (id.equals(deviceFilterId)) { + smartHomeDeviceHandler.updateChannelStates(allDevices, applianceIdToCapabilityStates); + } + } + + logger.debug("updateSmartHomeState finished"); + } catch (HttpException | JsonSyntaxException | ConnectionException e) { + logger.debug("updateSmartHomeState fails", e); + } catch (Exception e) { // this handler can be removed later, if we know that nothing else can fail. + logger.warn("updateSmartHomeState fails with unexpected error", e); + } + } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java index eabce0939a58a..457648b257c29 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/EchoHandler.java @@ -126,7 +126,7 @@ public class EchoHandler extends BaseThingHandler implements IEchoThingHandler { private boolean updateRoutine = true; private boolean updatePlayMusicVoiceCommand = true; private boolean updateStartCommand = true; - private @Nullable Integer noticationVolumeLevel; + private @Nullable Integer notificationVolumeLevel; private @Nullable Boolean ascendingAlarm; private @Nullable JsonPlaylists playLists; private @Nullable JsonNotificationSound @Nullable [] alarmSounds; @@ -302,7 +302,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof PercentType) { int volume = ((PercentType) command).intValue(); connection.notificationVolume(device, volume); - this.noticationVolumeLevel = volume; + this.notificationVolumeLevel = volume; waitForUpdate = -1; account.forceCheckData(); } @@ -657,7 +657,9 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (bluetoothRefresh) { JsonBluetoothStates states; states = connection.getBluetoothConnectionStates(); - state = states.findStateByDevice(device); + if (states != null) { + state = states.findStateByDevice(device); + } } updateState(account, device, state, null, null, null, null, null); @@ -699,7 +701,7 @@ private boolean handleEqualizerCommands(String channelId, Command command, Conne newEqualizerSetting.treble = value.intValue(); } try { - connection.SetEqualizer(device, newEqualizerSetting); + connection.setEqualizer(device, newEqualizerSetting); return true; } catch (HttpException | IOException | ConnectionException e) { logger.debug("Update equalizer failed", e); @@ -765,22 +767,22 @@ private void stopCurrentNotification() { } private void updateNotificationTimerState() { - boolean stopCurrentNotifcation = true; + boolean stopCurrentNotification = true; JsonNotificationResponse currentNotification = this.currentNotification; try { if (currentNotification != null) { Connection currentConnection = this.findConnection(); if (currentConnection != null) { JsonNotificationResponse newState = currentConnection.getNotificationState(currentNotification); - if (StringUtils.equals(newState.status, "ON")) { - stopCurrentNotifcation = false; + if (newState != null && "ON".equals(newState.status)) { + stopCurrentNotification = false; } } } } catch (IOException | URISyntaxException e) { logger.warn("update notification state fails", e); } - if (stopCurrentNotifcation) { + if (stopCurrentNotification) { if (currentNotification != null) { String type = currentNotification.type; if (type != null) { @@ -807,7 +809,7 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, this.logger.debug("Handle updateState {}", this.getThing().getUID()); if (deviceNotificationState != null) { - noticationVolumeLevel = deviceNotificationState.volumeLevel; + notificationVolumeLevel = deviceNotificationState.volumeLevel; } if (ascendingAlarmModel != null) { ascendingAlarm = ascendingAlarmModel.ascendingAlarmEnabled; @@ -851,44 +853,44 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, Progress progress = null; try { JsonPlayerState playerState = connection.getPlayer(device); - playerInfo = playerState.playerInfo; - if (playerInfo != null) { - infoText = playerInfo.infoText; - if (infoText == null) { - infoText = playerInfo.miniInfoText; - } - mainArt = playerInfo.mainArt; - provider = playerInfo.provider; - if (provider != null) { - musicProviderId = provider.providerName; - // Map the music provider id to the one used for starting music with voice command - if (musicProviderId != null) { - musicProviderId = musicProviderId.toUpperCase(); - - if (StringUtils.equals(musicProviderId, "AMAZON MUSIC")) { - musicProviderId = "AMAZON_MUSIC"; - } - if (StringUtils.equals(musicProviderId, "CLOUD_PLAYER")) { - musicProviderId = "AMAZON_MUSIC"; - } - if (StringUtils.startsWith(musicProviderId, "TUNEIN")) { - musicProviderId = "TUNEIN"; - } - if (StringUtils.startsWithIgnoreCase(musicProviderId, "iHeartRadio")) { - musicProviderId = "I_HEART_RADIO"; - } - if (StringUtils.containsIgnoreCase(musicProviderId, "Apple") - && StringUtils.containsIgnoreCase(musicProviderId, "Music")) { - musicProviderId = "APPLE_MUSIC"; + if (playerState != null) { + playerInfo = playerState.playerInfo; + if (playerInfo != null) { + infoText = playerInfo.infoText; + if (infoText == null) { + infoText = playerInfo.miniInfoText; + } + mainArt = playerInfo.mainArt; + provider = playerInfo.provider; + if (provider != null) { + musicProviderId = provider.providerName; + // Map the music provider id to the one used for starting music with voice command + if (musicProviderId != null) { + musicProviderId = musicProviderId.toUpperCase(); + + if (StringUtils.equals(musicProviderId, "AMAZON MUSIC")) { + musicProviderId = "AMAZON_MUSIC"; + } + if (StringUtils.equals(musicProviderId, "CLOUD_PLAYER")) { + musicProviderId = "AMAZON_MUSIC"; + } + if (StringUtils.startsWith(musicProviderId, "TUNEIN")) { + musicProviderId = "TUNEIN"; + } + if (StringUtils.startsWithIgnoreCase(musicProviderId, "iHeartRadio")) { + musicProviderId = "I_HEART_RADIO"; + } + if (StringUtils.containsIgnoreCase(musicProviderId, "Apple") + && StringUtils.containsIgnoreCase(musicProviderId, "Music")) { + musicProviderId = "APPLE_MUSIC"; + } } } + progress = playerInfo.progress; } - progress = playerInfo.progress; } } catch (HttpException e) { - if (e.getCode() == 400) { - // Ignore - } else { + if (e.getCode() != 400) { logger.info("getPlayer fails", e); } } catch (IOException | URISyntaxException e) { @@ -896,10 +898,8 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, } // check playing isPlaying = (playerInfo != null && StringUtils.equals(playerInfo.state, "PLAYING")); - // || (mediaState != null && StringUtils.equals(mediaState.currentState, "PLAYING")); isPaused = (playerInfo != null && StringUtils.equals(playerInfo.state, "PAUSED")); - // || (mediaState != null && StringUtils.equals(mediaState.currentState, "PAUSED")); synchronized (progressLock) { Boolean showTime = null; Long mediaLength = null; @@ -947,7 +947,6 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, } // handle music provider id - if (provider != null && isPlaying) { if (musicProviderId != null) { this.musicProviderId = musicProviderId; @@ -1129,8 +1128,9 @@ public void updateState(AccountHandler accountHandler, @Nullable Device device, updateState(CHANNEL_ASCENDING_ALARM, ascendingAlarm != null ? (ascendingAlarm ? OnOffType.ON : OnOffType.OFF) : UnDefType.UNDEF); - if (noticationVolumeLevel != null) { - updateState(CHANNEL_NOTIFICATION_VOLUME, new PercentType(noticationVolumeLevel)); + final Integer notificationVolumeLevel = this.notificationVolumeLevel; + if (notificationVolumeLevel != null) { + updateState(CHANNEL_NOTIFICATION_VOLUME, new PercentType(notificationVolumeLevel)); } else { updateState(CHANNEL_NOTIFICATION_VOLUME, UnDefType.UNDEF); } @@ -1155,14 +1155,16 @@ private void updateEqualizerState() { if (device == null) { return; } - Integer bass; - Integer midrange; - Integer treble; + Integer bass = null; + Integer midrange = null; + Integer treble = null; try { JsonEqualizer equalizer = connection.getEqualizer(device); - bass = equalizer.bass; - midrange = equalizer.mid; - treble = equalizer.treble; + if (equalizer != null) { + bass = equalizer.bass; + midrange = equalizer.mid; + treble = equalizer.treble; + } this.lastKnownEqualizer = equalizer; } catch (IOException | URISyntaxException | HttpException | ConnectionException e) { logger.debug("Get equalizer failes", e); @@ -1214,7 +1216,7 @@ public void handlePushActivity(Activity pushActivity) { if ("DISCARDED_NON_DEVICE_DIRECTED_INTENT".equals(pushActivity.activityStatus)) { return; } - Description description = pushActivity.ParseDescription(); + Description description = pushActivity.parseDescription(); if (StringUtils.isEmpty(description.firstUtteranceId) || StringUtils.startsWithIgnoreCase(description.firstUtteranceId, "TextClient:")) { return; diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java new file mode 100644 index 0000000000000..ccd55db530ed6 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/handler/SmartHomeDeviceHandler.java @@ -0,0 +1,388 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.DEVICE_PROPERTY_ID; +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.SUPPORTED_INTERFACES; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.thing.binding.BridgeHandler; +import org.eclipse.smarthome.core.thing.binding.builder.ChannelBuilder; +import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.StateDescription; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroups.SmartHomeGroup; +import org.openhab.binding.amazonechocontrol.internal.jsons.SmartHomeBaseDevice; +import org.openhab.binding.amazonechocontrol.internal.smarthome.Constants; +import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase; +import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase.ChannelInfo; +import org.openhab.binding.amazonechocontrol.internal.smarthome.HandlerBase.UpdateChannelResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class SmartHomeDeviceHandler extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceHandler.class); + + private @Nullable SmartHomeBaseDevice smartHomeBaseDevice; + private final Gson gson; + private final Map handlers = new HashMap<>(); + private final Map lastStates = new HashMap<>(); + + public SmartHomeDeviceHandler(Thing thing, Gson gson) { + super(thing); + this.gson = gson; + } + + public synchronized void setDeviceAndUpdateThingState(AccountHandler accountHandler, + @Nullable SmartHomeBaseDevice smartHomeBaseDevice) { + if (smartHomeBaseDevice == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Can't find smarthomeBaseDevice"); + return; + } + boolean changed = this.smartHomeBaseDevice == null; + this.smartHomeBaseDevice = smartHomeBaseDevice; + + Set unusedChannels = new HashSet<>(); + thing.getChannels().forEach(channel -> unusedChannels.add(channel.getUID().getId())); + + Set unusedHandlers = new HashSet<>(handlers.keySet()); + + Map> capabilities = new HashMap<>(); + getCapabilities(capabilities, accountHandler, smartHomeBaseDevice); + + ThingBuilder thingBuilder = editThing(); + + for (String interfaceName : capabilities.keySet()) { + HandlerBase handler = handlers.get(interfaceName); + if (handler != null) { + unusedHandlers.remove(interfaceName); + } else { + Supplier creator = Constants.HANDLER_FACTORY.get(interfaceName); + if (creator != null) { + handler = creator.get(); + handlers.put(interfaceName, handler); + } + } + if (handler != null) { + Collection required = handler.initialize(this, capabilities.get(interfaceName)); + for (ChannelInfo channelInfo : required) { + unusedChannels.remove(channelInfo.channelId); + if (addChannelToDevice(thingBuilder, channelInfo.channelId, channelInfo.itemType, + channelInfo.channelTypeUID)) { + changed = true; + } + } + } + } + + unusedHandlers.forEach(handlers::remove); + if (!unusedChannels.isEmpty()) { + changed = true; + unusedChannels.stream().map(id -> new ChannelUID(thing.getUID(), id)).forEach(thingBuilder::withoutChannel); + } + + if (changed) { + updateThing(thingBuilder.build()); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Thing has changed."); + accountHandler.forceDelayedSmartHomeStateUpdate(getId()); + } + } + + public String getId() { + String id = (String) getConfig().get(DEVICE_PROPERTY_ID); + if (id == null) { + return ""; + } + return id; + } + + @Override + public void updateState(String channelId, State state) { + super.updateState(new ChannelUID(thing.getUID(), channelId), state); + } + + @Override + public void initialize() { + AccountHandler accountHandler = getAccountHandler(); + if (accountHandler != null) { + accountHandler.addSmartHomeDeviceHandler(this); + setDeviceAndUpdateThingState(accountHandler, smartHomeBaseDevice); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridgehandler not found"); + } + } + + private boolean addChannelToDevice(ThingBuilder thingBuilder, String channelId, String itemType, + ChannelTypeUID channelTypeUID) { + Channel channel = thing.getChannel(channelId); + if (channel != null) { + if (channelTypeUID.equals(channel.getChannelTypeUID()) && itemType.equals(channel.getAcceptedItemType())) { + // channel exist with the same settings + return false; + } + // channel exist with other settings, remove it first + thingBuilder.withoutChannel(channel.getUID()); + } + thingBuilder.withChannel(ChannelBuilder.create(new ChannelUID(thing.getUID(), channelId), itemType) + .withType(channelTypeUID).build()); + return true; + } + + public void updateChannelStates(List allDevices, + Map applianceIdToCapabilityStates) { + AccountHandler accountHandler = getAccountHandler(); + SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice; + if (smartHomeBaseDevice == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Can't find smarthomeBaseDevice!"); + return; + } + + boolean stateFound = false; + Map> mapInterfaceToStates = new HashMap<>(); + SmartHomeDevice firstDevice = null; + for (SmartHomeDevice shd : getSupportedSmartHomeDevices(smartHomeBaseDevice, allDevices)) { + JsonArray states = applianceIdToCapabilityStates.get(shd.applianceId); + String applianceId = shd.applianceId; + if (applianceId == null) { + continue; + } + if (states != null) { + stateFound = true; + if (smartHomeBaseDevice.isGroup()) { + // for groups, store the last state of all devices + lastStates.put(applianceId, states); + } + } else { + states = lastStates.get(applianceId); + if (states == null) { + continue; + } + } + if (firstDevice == null) { + firstDevice = shd; + } + for (JsonElement stateElement : states) { + String stateJson = stateElement.getAsString(); + if (stateJson.startsWith("{") && stateJson.endsWith("}")) { + JsonObject state = gson.fromJson(stateJson, JsonObject.class); + String interfaceName = state.get("namespace").getAsString(); + mapInterfaceToStates.computeIfAbsent(interfaceName, k -> new ArrayList<>()).add(state); + } + } + } + for (HandlerBase handlerBase : handlers.values()) { + if (handlerBase == null) { + continue; + } + UpdateChannelResult result = new UpdateChannelResult(); + + for (String interfaceName : handlerBase.getSupportedInterface()) { + List stateList = mapInterfaceToStates.getOrDefault(interfaceName, Collections.emptyList()); + try { + handlerBase.updateChannels(interfaceName, stateList, result); + } catch (Exception e) { + // We catch all exceptions, otherwise all other things are not updated! + logger.debug("Updating states failed", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); + } + } + + if (result.needSingleUpdate && smartHomeBaseDevice instanceof SmartHomeDevice && accountHandler != null) { + SmartHomeDevice shd = (SmartHomeDevice) smartHomeBaseDevice; + accountHandler.forceDelayedSmartHomeStateUpdate(shd.findId()); + } + } + + if (stateFound) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "State not found"); + } + } + + private @Nullable AccountHandler getAccountHandler() { + Bridge bridge = getBridge(); + if (bridge != null) { + BridgeHandler bridgeHandler = bridge.getHandler(); + if (bridgeHandler instanceof AccountHandler) { + return (AccountHandler) bridgeHandler; + } + } + + return null; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + AccountHandler accountHandler = getAccountHandler(); + if (accountHandler == null) { + logger.debug("accountHandler is null in {}", thing.getUID()); + return; + } + Connection connection = accountHandler.findConnection(); + if (connection == null) { + logger.debug("connection is null in {}", thing.getUID()); + return; + } + + try { + if (command instanceof RefreshType) { + accountHandler.forceDelayedSmartHomeStateUpdate(getId()); + return; + } + SmartHomeBaseDevice smartHomeBaseDevice = this.smartHomeBaseDevice; + if (smartHomeBaseDevice == null) { + logger.debug("smarthomeBaseDevice is null in {}", thing.getUID()); + return; + } + Set devices = getSupportedSmartHomeDevices(smartHomeBaseDevice, + accountHandler.getLastKnownSmartHomeDevices()); + String channelId = channelUID.getId(); + + for (String interfaceName : handlers.keySet()) { + HandlerBase handlerBase = handlers.get(interfaceName); + if (handlerBase == null || !handlerBase.hasChannel(channelId)) { + continue; + } + for (SmartHomeDevice shd : devices) { + String entityId = shd.entityId; + if (entityId == null) { + continue; + } + SmartHomeCapability[] capabilities = shd.capabilities; + if (capabilities == null) { + logger.debug("capabilities is null in {}", thing.getUID()); + return; + } + accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // block updates + if (handlerBase.handleCommand(connection, shd, entityId, capabilities, channelUID.getId(), + command)) { + accountHandler.forceDelayedSmartHomeStateUpdate(getId()); // force update again to restart + // update timer + logger.debug("Command {} sent to {}", command, shd.findId()); + } + } + } + } catch (Exception e) { + logger.warn("Handle command failed", e); + } + } + + private static void getCapabilities(Map> result, AccountHandler accountHandler, + SmartHomeBaseDevice device) { + if (device instanceof SmartHomeDevice) { + SmartHomeDevice shd = (SmartHomeDevice) device; + SmartHomeCapability[] capabilities = shd.capabilities; + if (capabilities == null) { + return; + } + for (SmartHomeCapability capability : capabilities) { + String interfaceName = capability.interfaceName; + if (interfaceName != null) { + result.computeIfAbsent(interfaceName, name -> new ArrayList<>()).add(capability); + } + } + } + if (device instanceof SmartHomeGroup) { + for (SmartHomeDevice shd : getSupportedSmartHomeDevices(device, + accountHandler.getLastKnownSmartHomeDevices())) { + getCapabilities(result, accountHandler, shd); + } + } + } + + public static Set getSupportedSmartHomeDevices(@Nullable SmartHomeBaseDevice baseDevice, + List allDevices) { + if (baseDevice == null) { + return Collections.emptySet(); + } + Set result = new HashSet<>(); + if (baseDevice instanceof SmartHomeDevice) { + SmartHomeDevice shd = (SmartHomeDevice) baseDevice; + SmartHomeCapability[] capabilities = shd.capabilities; + if (capabilities != null) { + if (Arrays.stream(capabilities).map(capability -> capability.interfaceName) + .anyMatch(SUPPORTED_INTERFACES::contains)) { + result.add(shd); + } + } + } else { + SmartHomeGroup shg = (SmartHomeGroup) baseDevice; + for (SmartHomeBaseDevice device : allDevices) { + if (device instanceof SmartHomeDevice) { + SmartHomeDevice shd = (SmartHomeDevice) device; + if (shd.tags != null && shd.tags.tagNameToValueSetMap != null + && shd.tags.tagNameToValueSetMap.groupIdentity != null + && shg.applianceGroupIdentifier != null && shg.applianceGroupIdentifier.value != null + && Arrays.asList(shd.tags.tagNameToValueSetMap.groupIdentity) + .contains(shg.applianceGroupIdentifier.value)) { + SmartHomeCapability[] capabilities = shd.capabilities; + if (capabilities != null) { + if (Arrays.stream(capabilities).map(capability -> capability.interfaceName) + .anyMatch(SUPPORTED_INTERFACES::contains)) { + result.add(shd); + } + } + } + } + } + } + return result; + } + + public @Nullable StateDescription findStateDescription(Channel channel, StateDescription originalStateDescription, + @Nullable Locale locale) { + String channelId = channel.getUID().getId(); + for (HandlerBase handler : handlers.values()) { + if (handler != null && handler.hasChannel(channelId)) { + return handler.findStateDescription(channelId, originalStateDescription, locale); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonActivities.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonActivities.java index 321ab282bd21d..499e4617b474c 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonActivities.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonActivities.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.amazonechocontrol.internal.jsons; -import org.apache.commons.lang.StringUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -20,7 +19,7 @@ import com.google.gson.JsonSyntaxException; /** - * The {@link JsonActivity} encapsulate the GSON data of the push command for push activity + * The {@link JsonActivities} encapsulate the GSON data of the push command for push activity * * @author Michael Geramb - Initial contribution */ @@ -58,14 +57,16 @@ public static class Description { public @Nullable String firstStreamId; } - public Description ParseDescription() { + public Description parseDescription() { String description = this.description; - if (StringUtils.isEmpty(description) || !description.startsWith("{") || !description.endsWith("}")) { + if (description == null || description.isEmpty() || !description.startsWith("{") + || !description.endsWith("}")) { return new Description(); } Gson gson = new Gson(); try { - return gson.fromJson(description, Description.class); + Description description1 = gson.fromJson(description, Description.class); + return description1 != null ? description1 : new Description(); } catch (JsonSyntaxException e) { return new Description(); } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java index 5612805da66b8..29c5393147dcc 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAnnouncementTarget.java @@ -16,7 +16,8 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonActivity} encapsulate the GSON data of the sequence command AlexaAnnouncement for announcement target + * The {@link JsonAnnouncementTarget} encapsulate the GSON data of the sequence command AlexaAnnouncement for + * announcement target * * @author Michael Geramb - Initial contribution */ diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java index 444a2ec67c461..6f4015e22db46 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonAscendingAlarm.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonActivity} encapsulate the GSON data of the /api/ascending-alarm response + * The {@link JsonAscendingAlarm} encapsulate the GSON data of the /api/ascending-alarm response * * @author Michael Geramb - Initial contribution */ diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java new file mode 100644 index 0000000000000..f40d2edafd971 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColorTemperature.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonColorTemperature { + public @Nullable String temperatureName; + + public JsonColorTemperature(String temperature) { + temperatureName = temperature; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColors.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColors.java new file mode 100644 index 0000000000000..65a79cc89d74e --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonColors.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonColors { + public @Nullable String colorName; + + public JsonColors(String colorName) { + this.colorName = colorName; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java index e62fcdf02b7f0..cff02a45403da 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushActivity.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonPushPayloadCommand} encapsulate the GSON data of the push command with device information + * The {@link JsonCommandPayloadPushActivity} encapsulate the GSON data of the push command with device information * * @author Michael Geramb - Initial contribution */ diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java index 95fee29973f9a..4abcd34454d07 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonCommandPayloadPushDevice.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonPushPayloadCommand} encapsulate the GSON data of the push command with device information + * The {@link JsonCommandPayloadPushDevice} encapsulate the GSON data of the push command with device information * * @author Michael Geramb - Initial contribution */ diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java index 1d5093860c7b0..ee1cf48f0a275 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonDeviceNotificationState.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonActivity} encapsulate the GSON data of the /api/device-notification-state response + * The {@link JsonDeviceNotificationState} encapsulate the GSON data of the /api/device-notification-state response * * @author Michael Geramb - Initial contribution */ diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java index dc357c3cac3c7..627f01b79eed4 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonExchangeTokenResponse.java @@ -17,6 +17,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** * The {@link JsonExchangeTokenResponse} encapsulate the GSON response data of the token exchange * @@ -35,11 +37,17 @@ public static class Tokens { } public static class Cookie { - public @Nullable String Path; - public @Nullable Boolean Secure; - public @Nullable String Value; - public @Nullable String Expires; - public @Nullable Boolean HttpOnly; - public @Nullable String Name; + @SerializedName("Path") + public @Nullable String path; + @SerializedName("Secure") + public @Nullable Boolean secure; + @SerializedName("Value") + public @Nullable String value; + @SerializedName("Expires") + public @Nullable String expires; + @SerializedName("HttpOnly") + public @Nullable Boolean httpOnly; + @SerializedName("Name") + public @Nullable String name; } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java new file mode 100644 index 0000000000000..34617fd42c637 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonNetworkDetails.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link JsonNetworkDetails} encapsulate the GSON data of a network query + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class JsonNetworkDetails { + public @Nullable String networkDetail; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java index 878ce532d9a3d..5e3a7d629c730 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppRequest.java @@ -15,52 +15,65 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** - * The {@link JsonRegisterApp} encapsulate the GSON data of register application request + * The {@link JsonRegisterAppRequest} encapsulate the GSON data of register application request * * @author Michael Geramb - Initial contribution */ @NonNullByDefault public class JsonRegisterAppRequest { - public JsonRegisterAppRequest(String serial, String access_token, String frc, JsonWebSiteCookie[] webSiteCookies) { - registration_data.device_serial = serial; - auth_data.access_token = access_token; - user_context_map.frc = frc; - cookies.website_cookies = webSiteCookies; + public JsonRegisterAppRequest(String serial, String accessToken, String frc, JsonWebSiteCookie[] webSiteCookies) { + registrationData.deviceSerial = serial; + authData.accessToken = accessToken; + userContextMap.frc = frc; + cookies.webSiteCookies = webSiteCookies; } - public String[] requested_extensions = { "device_info", "customer_info" }; + @SerializedName("requested_extensions") + public String[] requestedExtensions = { "device_info", "customer_info" }; public Cookies cookies = new Cookies(); - public RegistrationData registration_data = new RegistrationData(); - public AuthData auth_data = new AuthData(); - public UserContextMap user_context_map = new UserContextMap(); - public String[] requested_token_type = { "bearer", "mac_dms", "website_cookies" }; + @SerializedName("registration_data") + public RegistrationData registrationData = new RegistrationData(); + @SerializedName("auth_data") + public AuthData authData = new AuthData(); + @SerializedName("user_context_map") + public UserContextMap userContextMap = new UserContextMap(); + @SerializedName("requested_token_type") + public String[] requestedTokenType = { "bearer", "mac_dms", "website_cookies" }; public static class Cookies { - @Nullable - public JsonWebSiteCookie @Nullable [] website_cookies; - @Nullable - public String domain = ".amazon.com"; + @SerializedName("website_cookies") + public @Nullable JsonWebSiteCookie @Nullable [] webSiteCookies; + public @Nullable String domain = ".amazon.com"; } public static class RegistrationData { public String domain = "Device"; - public String app_version = "2.2.223830.0"; - public String device_type = "A2IVLV5VM2W81"; - public String device_name = "%FIRST_NAME%'s%DUPE_STRATEGY_1ST%Open HAB Alexa Binding"; - public String os_version = "11.4.1"; - @Nullable - public String device_serial; - public String device_model = "iPhone"; - public String app_name = "Open HAB Alexa Binding";// Amazon Alexa"; - public String software_version = "1"; + @SerializedName("app_version") + public String appVersion = "2.2.223830.0"; + @SerializedName("device_type") + public String deviceType = "A2IVLV5VM2W81"; + @SerializedName("device_name") + public String deviceName = "%FIRST_NAME%'s%DUPE_STRATEGY_1ST%openHAB Alexa Binding"; + @SerializedName("os_version") + public String osVersion = "11.4.1"; + @SerializedName("device_serial") + public @Nullable String deviceSerial; + @SerializedName("device_model") + public String deviceModel = "iPhone"; + @SerializedName("app_name") + public String appName = "openHAB Alexa Binding"; + @SerializedName("software_version") + public String softwareVersion = "1"; } public static class AuthData { - @Nullable - public String access_token; + @SerializedName("access_token") + public @Nullable String accessToken; } public static class UserContextMap { diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java index 277e815d03b30..de7e1569b4a94 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRegisterAppResponse.java @@ -15,6 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** * The {@link JsonRegisterAppResponse} encapsulate the GSON data of response from the register command * @@ -23,79 +25,88 @@ @NonNullByDefault public class JsonRegisterAppResponse { - @Nullable - public Response response; + public @Nullable Response response; - @Nullable - public String request_id; + @SerializedName("request_id") + public @Nullable String requestId; public static class Response { - @Nullable - public Success success; + public @Nullable Success success; } public static class Success { - @Nullable - public Extensions extensions; - @Nullable - public Tokens tokens; - @Nullable - public String customer_id; + public @Nullable Extensions extensions; + + public @Nullable Tokens tokens; + + @SerializedName("customer_id") + public @Nullable String customerId; } public static class Extensions { - @Nullable - public DeviceInfo device_info; - @Nullable - public CustomerInfo customer_info; - @Nullable - public String customer_id; + @SerializedName("device_info") + public @Nullable DeviceInfo deviceInfo; + + @SerializedName("customer_info") + public @Nullable CustomerInfo customerInfo; + + @SerializedName("customer_id") + public @Nullable String customerId; } public static class DeviceInfo { - @Nullable - public String device_name; - @Nullable - public String device_serial_number; - @Nullable - public String device_type; + @SerializedName("device_name") + public @Nullable String deviceName; + + @SerializedName("device_serial_number") + public @Nullable String deviceSerialNumber; + + @SerializedName("device_type") + public @Nullable String deviceType; } public static class CustomerInfo { - @Nullable - public String account_pool; - @Nullable - public String user_id; - @Nullable - public String home_region; - @Nullable - public String name; - @Nullable - public String given_name; + @SerializedName("account_pool") + public @Nullable String accountPool; + + @SerializedName("user_id") + public @Nullable String userId; + + @SerializedName("home_region") + public @Nullable String homeRegion; + + public @Nullable String name; + + @SerializedName("given_name") + public @Nullable String givenName; } public static class Tokens { - @Nullable - public Object website_cookies; - @Nullable - public MacDms mac_dms; - @Nullable - public Bearer bearer; + @SerializedName("website_cookies") + public @Nullable Object websiteCookies; + + @SerializedName("mac_dms") + public @Nullable MacDms macDms; + + public @Nullable Bearer bearer; } public static class MacDms { - @Nullable - public String device_private_key; - @Nullable - public String adp_token; + @SerializedName("device_private_key") + public @Nullable String devicePrivateKey; + + @SerializedName("adp_token") + public @Nullable String adpToken; } public static class Bearer { - @Nullable - public String access_token; - @Nullable - public String refresh_token; - @Nullable - public String expires_in; + @SerializedName("access_token") + public @Nullable String accessToken; + + @SerializedName("refresh_token") + public @Nullable String refreshToken; + + @SerializedName("expires_in") + public @Nullable String expiresIn; } } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java index 7133fb8451963..659b4089b4ef3 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonRenewTokenResponse.java @@ -15,6 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** * The {@link JsonRenewTokenResponse} encapsulate the GSON response of the renew token request * @@ -22,7 +24,10 @@ */ @NonNullByDefault public class JsonRenewTokenResponse { - public @Nullable String access_token; - public @Nullable String token_type; - public @Nullable Long expires_in; + @SerializedName("access_token") + public @Nullable String accessToken; + @SerializedName("token_type") + public @Nullable String tokenType; + @SerializedName("expires_in") + public @Nullable Long expiresIn; } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapabilities.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapabilities.java new file mode 100644 index 0000000000000..e136c1abe2bfd --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeCapabilities.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeCapabilities { + + public static class SmartHomeCapability { + public @Nullable String capabilityType; + public @Nullable String type; + public @Nullable String version; + public @Nullable String interfaceName; + public @Nullable Properties properties; + } + + public static class Properties { + public @Nullable Property @Nullable [] supported; + } + + public static class Property { + public @Nullable String name; + } + + public @Nullable SmartHomeCapability @Nullable [] capabilites; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java new file mode 100644 index 0000000000000..46d7e42cd7fc6 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceAlias.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeDeviceAlias { + public @Nullable String friendlyName; + public @Nullable Boolean enabled; + + public JsonSmartHomeDeviceAlias(String friendlyName, Boolean enabled) { + this.friendlyName = friendlyName; + this.enabled = enabled; + } + + public JsonSmartHomeDeviceAlias() { + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java new file mode 100644 index 0000000000000..0d66349f393b7 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDeviceNetworkState.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Lukas Knoeller - Initial contribution + * + */ +@NonNullByDefault +public class JsonSmartHomeDeviceNetworkState { + public static class SmartHomeDeviceNetworkState { + public @Nullable String reachability; + } + + public @Nullable SmartHomeDeviceNetworkState networkState; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevices.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevices.java new file mode 100644 index 0000000000000..ea6584b7bbccf --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeDevices.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDeviceNetworkState.SmartHomeDeviceNetworkState; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeTags.JsonSmartHomeTag; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeDevices { + public static class SmartHomeDevice implements SmartHomeBaseDevice { + + public @Nullable Integer updateIntervalInSeconds; + + @Override + public @Nullable String findId() { + return applianceId; + } + + @Override + public boolean isGroup() { + return false; + } + + public @Nullable String applianceId; + public @Nullable String manufacturerName; + public @Nullable String friendlyDescription; + public @Nullable String modelName; + public @Nullable String friendlyName; + public @Nullable String reachability; + public @Nullable String entityId; + public @Nullable SmartHomeDeviceNetworkState applianceNetworkState; + public @Nullable SmartHomeCapability @Nullable [] capabilities; + public @Nullable JsonSmartHomeTag tags; + public @Nullable String @Nullable [] applianceTypes; + public @Nullable JsonSmartHomeDeviceAlias @Nullable [] aliases; + public @Nullable SmartHomeDevice @Nullable [] groupDevices; + public @Nullable String connectedVia; + public @Nullable DriverIdentity driverIdentity; + } + + public static class DriverIdentity { + public @Nullable String namespace; + public @Nullable String identifier; + } + + public @Nullable SmartHomeDevice @Nullable [] smarthomeDevices; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java new file mode 100644 index 0000000000000..ce92ea8009558 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentifiers.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeGroupIdentifiers { + + public static class SmartHomeGroupIdentifier { + public @Nullable String value; + } + + public @Nullable SmartHomeGroupIdentifier identifier; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java new file mode 100644 index 0000000000000..06deba378bf7b --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroupIdentity.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * + * @author Lukas Knoeller - Initial contribution + * + */ +@NonNullByDefault +public class JsonSmartHomeGroupIdentity { + public static class SmartHomeGroupIdentity { + public @Nullable String @Nullable [] groupIdentity; + } + + public @Nullable SmartHomeGroupIdentity @Nullable [] groupIdentity; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java new file mode 100644 index 0000000000000..4281534ee84dd --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeGroups.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentifiers.SmartHomeGroupIdentifier; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeGroups { + + public static class SmartHomeGroup implements SmartHomeBaseDevice { + + @Override + public @Nullable String findId() { + SmartHomeGroupIdentifier applianceGroupIdentifier = this.applianceGroupIdentifier; + if (applianceGroupIdentifier == null) { + return null; + } + String value = applianceGroupIdentifier.value; + if (value == null) { + return null; + } + return value; + } + + @Override + public boolean isGroup() { + return true; + } + + public @Nullable String applianceGroupName; + public @Nullable Boolean isSpace; + public @Nullable Boolean space; + public @Nullable SmartHomeGroupIdentifier applianceGroupIdentifier; + } + + public @Nullable SmartHomeGroup @Nullable [] groups; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java new file mode 100644 index 0000000000000..6873c2cbd9bbc --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonSmartHomeTags.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeGroupIdentity.SmartHomeGroupIdentity; + +/** + * @author Lukas Knoeller - Initial contribution + */ +@NonNullByDefault +public class JsonSmartHomeTags { + public static class JsonSmartHomeTag { + public @Nullable SmartHomeGroupIdentity tagNameToValueSetMap; + } + + public @Nullable SmartHomeGroupIdentity tagNameToValueSetMap; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java index e95ced344848f..cda9d0380fe14 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonTokenResponse.java @@ -15,18 +15,19 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** - * The {@link JsonActivity} encapsulate the GSON data of the token response + * The {@link JsonTokenResponse} encapsulate the GSON data of the token response * * @author Michael Geramb - Initial contribution */ @NonNullByDefault public class JsonTokenResponse { - - @Nullable - public String access_token; - @Nullable - public String token_type; - @Nullable - public Integer expires_in; + @SerializedName("access_token") + public @Nullable String accessToken; + @SerializedName("token_type") + public @Nullable String tokenType; + @SerializedName("expires_in") + public @Nullable Integer expiresIn; } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java index 87b283a3fee68..58a2e758c64dd 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonUsersMeResponse.java @@ -16,32 +16,21 @@ import org.eclipse.jdt.annotation.Nullable; /** - * The {@link JsonActivity} encapsulate the GSON data of the users me response + * The {@link JsonUsersMeResponse} encapsulate the GSON data of the users me response * * @author Michael Geramb - Initial contribution */ @NonNullByDefault public class JsonUsersMeResponse { - @Nullable - public String countryOfResidence; - @Nullable - public String effectiveMarketPlaceId; - @Nullable - public String email; - @Nullable - public Boolean eulaAcceptance; - @Nullable - public String @Nullable [] features; - @Nullable - public String fullName; - @Nullable - public Boolean hasActiveDopplers; - @Nullable - public String id; - @Nullable - public String marketPlaceDomainName; - @Nullable - public String marketPlaceId; - @Nullable - public String marketPlaceLocale; + public @Nullable String countryOfResidence; + public @Nullable String effectiveMarketPlaceId; + public @Nullable String email; + public @Nullable Boolean eulaAcceptance; + public @Nullable String @Nullable [] features; + public @Nullable String fullName; + public @Nullable Boolean hasActiveDopplers; + public @Nullable String id; + public @Nullable String marketPlaceDomainName; + public @Nullable String marketPlaceId; + public @Nullable String marketPlaceLocale; } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java index 948b01711983b..f35975530be51 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/JsonWebSiteCookie.java @@ -15,6 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.SerializedName; + /** * The {@link JsonWebSiteCookie} encapsulate the GSON data of register cookie array * @@ -23,12 +25,12 @@ @NonNullByDefault public class JsonWebSiteCookie { public JsonWebSiteCookie(String name, String value) { - Name = name; - Value = value; + this.name = name; + this.value = value; } - @Nullable - public String Value; - @Nullable - public String Name; + @SerializedName("Value") + public @Nullable String value; + @SerializedName("Name") + public @Nullable String name; } diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java new file mode 100644 index 0000000000000..14cab708f502b --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/jsons/SmartHomeBaseDevice.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.jsons; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link SmartHomeBaseDevice} is the base interface for all smart home device json nodes + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public interface SmartHomeBaseDevice { + @Nullable + String findId(); + + boolean isGroup(); +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/Constants.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/Constants.java new file mode 100644 index 0000000000000..f6d8e393fe0b9 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/Constants.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; + +/** + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class Constants { + public static final Map> HANDLER_FACTORY = new HashMap<>(); + + static { + HANDLER_FACTORY.put(HandlerPowerController.INTERFACE, HandlerPowerController::new); + HANDLER_FACTORY.put(HandlerBrightnessController.INTERFACE, HandlerBrightnessController::new); + HANDLER_FACTORY.put(HandlerColorController.INTERFACE, HandlerColorController::new); + HANDLER_FACTORY.put(HandlerColorTemperatureController.INTERFACE, HandlerColorTemperatureController::new); + HANDLER_FACTORY.put(HandlerSecurityPanelController.INTERFACE, HandlerSecurityPanelController::new); + HANDLER_FACTORY.put(HandlerAcousticEventSensor.INTERFACE, HandlerAcousticEventSensor::new); + HANDLER_FACTORY.put(HandlerTemperatureSensor.INTERFACE, HandlerTemperatureSensor::new); + HANDLER_FACTORY.put(HandlerPercentageController.INTERFACE, HandlerPercentageController::new); + HANDLER_FACTORY.put(HandlerPowerLevelController.INTERFACE, HandlerPowerLevelController::new); + } + + public static final Set SUPPORTED_INTERFACES = HANDLER_FACTORY.keySet(); + + // channel types + public static final ChannelTypeUID CHANNEL_TYPE_TEMPERATURE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "temperature"); + + // List of Item types + public static final String ITEM_TYPE_SWITCH = "Switch"; + public static final String ITEM_TYPE_DIMMER = "Dimmer"; + public static final String ITEM_TYPE_STRING = "String"; + public static final String ITEM_TYPE_NUMBER = "Number"; + public static final String ITEM_TYPE_NUMBER_TEMPERATURE = "Number:Temperature"; + public static final String ITEM_TYPE_CONTACT = "Contact"; + public static final String ITEM_TYPE_COLOR = "Color"; +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/DynamicStateDescriptionSmartHome.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/DynamicStateDescriptionSmartHome.java new file mode 100644 index 0000000000000..e1916119e87a7 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/DynamicStateDescriptionSmartHome.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants.BINDING_ID; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingRegistry; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.thing.type.DynamicStateDescriptionProvider; +import org.eclipse.smarthome.core.types.StateDescription; +import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; +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 colors of the smart bulbs + * + * @author Lukas Knoeller - Initial contribution + * + */ + +@Component(service = { DynamicStateDescriptionProvider.class, DynamicStateDescriptionSmartHome.class }) +@NonNullByDefault +public class DynamicStateDescriptionSmartHome implements DynamicStateDescriptionProvider { + private final ThingRegistry thingRegistry; + + @Activate + public DynamicStateDescriptionSmartHome(@Reference ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + + public @Nullable SmartHomeDeviceHandler findHandler(Channel channel) { + Thing thing = thingRegistry.get(channel.getUID().getThingUID()); + if (thing == null) { + return null; + } + ThingHandler handler = thing.getHandler(); + if (!(handler instanceof SmartHomeDeviceHandler)) { + return null; + } + SmartHomeDeviceHandler smartHomeHandler = (SmartHomeDeviceHandler) handler; + return smartHomeHandler; + } + + @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; + } + SmartHomeDeviceHandler handler = findHandler(channel); + if (handler != null) { + return handler.findStateDescription(channel, originalStateDescription, locale); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java new file mode 100644 index 0000000000000..3c4b7bd70828e --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerAcousticEventSensor.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_CONTACT; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerAcousticEventSensor} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerAcousticEventSensor extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.AcousticEventSensor"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_GLASS_BREAK_DETECTION_STATE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "glassBreakDetectionState"); + private static final ChannelTypeUID CHANNEL_TYPE_SMOKE_ALARM_DETECTION_STATE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "smokeAlarmDetectionState"); + + // Channel definitions + private static final ChannelInfo GLASS_BREAK_DETECTION_STATE = new ChannelInfo( + "glassBreakDetectionState" /* propertyName */ , "glassBreakDetectionState" /* ChannelId */, + CHANNEL_TYPE_GLASS_BREAK_DETECTION_STATE /* Channel Type */ , ITEM_TYPE_CONTACT /* Item Type */); + private static final ChannelInfo SMOKE_ALARM_DETECTION_STATE = new ChannelInfo( + "smokeAlarmDetectionState" /* propertyName */ , "smokeAlarmDetectionState" /* ChannelId */, + CHANNEL_TYPE_SMOKE_ALARM_DETECTION_STATE /* Channel Type */ , ITEM_TYPE_CONTACT /* Item Type */); + + private ChannelInfo[] getAlarmChannels() { + return new ChannelInfo[] { GLASS_BREAK_DETECTION_STATE, SMOKE_ALARM_DETECTION_STATE }; + } + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + for (ChannelInfo channelInfo : getAlarmChannels()) { + if (channelInfo.propertyName.equals(property)) { + return new ChannelInfo[] { channelInfo }; + } + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + Boolean glassBreakDetectionStateValue = null; + Boolean smokeAlarmDetectionStateValue = null; + for (JsonObject state : stateList) { + if (GLASS_BREAK_DETECTION_STATE.propertyName.equals(state.get("name").getAsString())) { + if (glassBreakDetectionStateValue == null) { + glassBreakDetectionStateValue = !"NOT_DETECTED" + .equals(state.get("value").getAsJsonObject().get("value").getAsString()); + } + } else if (SMOKE_ALARM_DETECTION_STATE.propertyName.equals(state.get("name").getAsString())) { + if (smokeAlarmDetectionStateValue == null) { + smokeAlarmDetectionStateValue = !"NOT_DETECTED" + .equals(state.get("value").getAsJsonObject().get("value").getAsString()); + } + } + } + updateState(GLASS_BREAK_DETECTION_STATE.channelId, glassBreakDetectionStateValue == null ? UnDefType.UNDEF + : (glassBreakDetectionStateValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + updateState(SMOKE_ALARM_DETECTION_STATE.channelId, smokeAlarmDetectionStateValue == null ? UnDefType.UNDEF + : (smokeAlarmDetectionStateValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelUID, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBase.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBase.java new file mode 100644 index 0000000000000..8def553715988 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBase.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +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.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.StateDescription; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.Properties; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.Property; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public abstract class HandlerBase { + protected @Nullable SmartHomeDeviceHandler smartHomeDeviceHandler; + protected Map channels = new HashMap<>(); + + protected abstract ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property); + + public abstract void updateChannels(String interfaceName, List stateList, UpdateChannelResult result); + + public abstract boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException; + + public abstract @Nullable StateDescription findStateDescription(String channelId, + StateDescription originalStateDescription, @Nullable Locale locale); + + public boolean hasChannel(String channelId) { + return channels.containsKey(channelId); + } + + public abstract String[] getSupportedInterface(); + + SmartHomeDeviceHandler getSmartHomeDeviceHandler() throws IllegalStateException { + SmartHomeDeviceHandler smartHomeDeviceHandler = this.smartHomeDeviceHandler; + if (smartHomeDeviceHandler == null) { + throw new IllegalStateException("Handler not initialized"); + } + return smartHomeDeviceHandler; + } + + public Collection initialize(SmartHomeDeviceHandler smartHomeDeviceHandler, + List capabilities) { + this.smartHomeDeviceHandler = smartHomeDeviceHandler; + Map channels = new HashMap<>(); + for (SmartHomeCapability capability : capabilities) { + Properties properties = capability.properties; + if (properties != null) { + Property @Nullable [] supported = properties.supported; + if (supported != null) { + for (Property property : supported) { + if (property != null) { + String name = property.name; + if (name != null) { + ChannelInfo[] channelInfos = findChannelInfos(capability, name); + if (channelInfos != null) { + for (ChannelInfo channelInfo : channelInfos) { + if (channelInfo != null) { + channels.put(channelInfo.channelId, channelInfo); + } + } + } + } + } + } + } + } + } + this.channels = channels; + return channels.values(); + } + + protected boolean containsCapabilityProperty(SmartHomeCapability[] capabilties, String propertyName) { + for (SmartHomeCapability capability : capabilties) { + Properties properties = capability.properties; + if (properties != null) { + Property @Nullable [] supportedProperties = properties.supported; + if (supportedProperties != null) { + for (Property property : supportedProperties) { + if (property != null) { + if (propertyName != null && propertyName.equals(property.name)) { + return true; + } + } + } + } + } + } + return false; + } + + public void updateState(String channelId, State state) { + getSmartHomeDeviceHandler().updateState(channelId, state); + } + + public static class ChannelInfo { + public final String propertyName; + public final String channelId; + public final String itemType; + public ChannelTypeUID channelTypeUID; + + public ChannelInfo(String propertyName, String channelId, ChannelTypeUID channelTypeUID, String itemType) { + this.propertyName = propertyName; + this.channelId = channelId; + this.itemType = itemType; + this.channelTypeUID = channelTypeUID; + } + } + + public static class UpdateChannelResult { + public boolean needSingleUpdate; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java new file mode 100644 index 0000000000000..7cf92fd8b9309 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerBrightnessController.java @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_DIMMER; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerBrightnessController} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerBrightnessController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.BrightnessController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_BRIGHTNESS = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "brightness"); + + // Channel definitions + private static final ChannelInfo BRIGHTNESS = new ChannelInfo("brightness" /* propertyName */ , + "brightness" /* ChannelId */, CHANNEL_TYPE_BRIGHTNESS /* Channel Type */ , + ITEM_TYPE_DIMMER /* Item Type */); + + private @Nullable Integer lastBrightness; + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (BRIGHTNESS.propertyName.equals(property)) { + return new ChannelInfo[] { BRIGHTNESS }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + Integer brightnessValue = null; + for (JsonObject state : stateList) { + if (BRIGHTNESS.propertyName.equals(state.get("name").getAsString())) { + int value = state.get("value").getAsInt(); + // For groups take the maximum + if (brightnessValue == null) { + brightnessValue = value; + } else if (value > brightnessValue) { + brightnessValue = value; + } + } + } + if (brightnessValue != null) { + lastBrightness = brightnessValue; + } + updateState(BRIGHTNESS.channelId, brightnessValue == null ? UnDefType.UNDEF : new PercentType(brightnessValue)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(BRIGHTNESS.channelId)) { + if (containsCapabilityProperty(capabilties, BRIGHTNESS.propertyName)) { + if (command.equals(IncreaseDecreaseType.INCREASE)) { + Integer lastBrightness = this.lastBrightness; + if (lastBrightness != null) { + int newValue = lastBrightness++; + if (newValue > 100) { + newValue = 100; + } + this.lastBrightness = newValue; + connection.smartHomeCommand(entityId, "setBrightness", BRIGHTNESS.propertyName, newValue); + return true; + } + } else if (command.equals(IncreaseDecreaseType.DECREASE)) { + Integer lastBrightness = this.lastBrightness; + if (lastBrightness != null) { + int newValue = lastBrightness--; + if (newValue < 0) { + newValue = 0; + } + this.lastBrightness = newValue; + connection.smartHomeCommand(entityId, "setBrightness", BRIGHTNESS.propertyName, newValue); + return true; + } + } else if (command.equals(OnOffType.OFF)) { + lastBrightness = 0; + connection.smartHomeCommand(entityId, "setBrightness", BRIGHTNESS.propertyName, 0); + return true; + } else if (command.equals(OnOffType.ON)) { + lastBrightness = 100; + connection.smartHomeCommand(entityId, "setBrightness", BRIGHTNESS.propertyName, 100); + return true; + } else if (command instanceof PercentType) { + lastBrightness = ((PercentType) command).intValue(); + connection.smartHomeCommand(entityId, "setBrightness", BRIGHTNESS.propertyName, + ((PercentType) command).floatValue() / 100); + return true; + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java new file mode 100644 index 0000000000000..561f34105859a --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorController.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_COLOR; +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_STRING; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerColorController} is responsible for the Alexa.ColorTemperatureController + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerColorController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.ColorController"; + public static final String INTERFACE_COLOR_PROPERTIES = "Alexa.ColorPropertiesController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_COLOR_NAME = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "colorName"); + + private static final ChannelTypeUID CHANNEL_TYPE_COLOR = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "color"); + + // Channel and Properties + private static final ChannelInfo COLOR = new ChannelInfo("color" /* propertyName */, "color" /* ChannelId */, + CHANNEL_TYPE_COLOR /* Channel Type */, ITEM_TYPE_COLOR /* Item Type */); + + private static final ChannelInfo COLOR_PROPERTIES = new ChannelInfo("colorProperties" /* propertyName */, + "colorName" /* ChannelId */, CHANNEL_TYPE_COLOR_NAME /* Channel Type */, ITEM_TYPE_STRING /* Item Type */); + + private @Nullable HSBType lastColor; + private @Nullable String lastColorName; + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE, INTERFACE_COLOR_PROPERTIES }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (COLOR.propertyName.contentEquals(property)) { + return new ChannelInfo[] { COLOR, COLOR_PROPERTIES }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + if (INTERFACE.equals(interfaceName)) { + // WRITING TO THIS CHANNEL DOES CURRENTLY NOT WORK, BUT WE LEAVE THE CODE FOR FUTURE USE! + HSBType colorValue = null; + for (JsonObject state : stateList) { + if (COLOR.propertyName.equals(state.get("name").getAsString())) { + JsonObject value = state.get("value").getAsJsonObject(); + // For groups take the maximum + if (colorValue == null) { + colorValue = new HSBType(new DecimalType(value.get("hue").getAsInt()), + new PercentType(value.get("saturation").getAsInt() * 100), + new PercentType(value.get("brightness").getAsInt() * 100)); + } + } + } + if (colorValue != null) { + if (!colorValue.equals(lastColor)) { + result.needSingleUpdate = true; + lastColor = colorValue; + } + } + updateState(COLOR.channelId, colorValue == null ? UnDefType.UNDEF : colorValue); + } + if (INTERFACE_COLOR_PROPERTIES.equals(interfaceName)) { + String colorNameValue = null; + for (JsonObject state : stateList) { + if (COLOR_PROPERTIES.propertyName.equals(state.get("name").getAsString())) { + if (colorNameValue == null) { + result.needSingleUpdate = false; + colorNameValue = state.get("value").getAsJsonObject().get("name").getAsString(); + } + } + } + if (lastColorName == null) { + lastColorName = colorNameValue; + } else if (colorNameValue == null && lastColorName != null) { + colorNameValue = lastColorName; + } + updateState(COLOR_PROPERTIES.channelId, + lastColorName == null ? UnDefType.UNDEF : new StringType(lastColorName)); + } + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(COLOR.channelId)) { + if (containsCapabilityProperty(capabilties, COLOR.propertyName)) { + if (command instanceof HSBType) { + HSBType color = ((HSBType) command); + JsonObject colorObject = new JsonObject(); + colorObject.addProperty("hue", color.getHue()); + colorObject.addProperty("saturation", color.getSaturation().floatValue() / 100); + colorObject.addProperty("brightness", color.getBrightness().floatValue() / 100); + connection.smartHomeCommand(entityId, "setColor", "color", colorObject); + } + } + } + if (channelId.equals(COLOR_PROPERTIES.channelId)) { + if (containsCapabilityProperty(capabilties, COLOR.propertyName)) { + if (command instanceof StringType) { + String colorName = ((StringType) command).toFullString(); + if (StringUtils.isNotEmpty(colorName)) { + lastColorName = colorName; + connection.smartHomeCommand(entityId, "setColor", "colorName", colorName); + return true; + } + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java new file mode 100644 index 0000000000000..29a267c1f056b --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerColorTemperatureController.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_NUMBER; +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_STRING; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerColorTemperatureController} is responsible for the Alexa.ColorTemperatureController + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerColorTemperatureController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.ColorTemperatureController"; + public static final String INTERFACE_COLOR_PROPERTIES = "Alexa.ColorPropertiesController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEMPERATURE_NAME = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "colorTemperatureName"); + + private static final ChannelTypeUID CHANNEL_TYPE_COLOR_TEPERATURE_IN_KELVIN = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "colorTemperatureInKelvin"); + + // Channel and Properties + private static final ChannelInfo COLOR_TEMPERATURE_IN_KELVIN = new ChannelInfo( + "colorTemperatureInKelvin" /* propertyName */ , "colorTemperatureInKelvin" /* ChannelId */, + CHANNEL_TYPE_COLOR_TEPERATURE_IN_KELVIN /* Channel Type */ , ITEM_TYPE_NUMBER /* Item Type */); + + private static final ChannelInfo COLOR_TEMPERATURE_NAME = new ChannelInfo("colorProperties" /* propertyName */ , + "colorTemperatureName" /* ChannelId */, CHANNEL_TYPE_COLOR_TEMPERATURE_NAME /* Channel Type */ , + ITEM_TYPE_STRING /* Item Type */); + + private @Nullable Integer lastColorTemperature; + private @Nullable String lastColorName; + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE, INTERFACE_COLOR_PROPERTIES }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (COLOR_TEMPERATURE_IN_KELVIN.propertyName.contentEquals(property)) { + return new ChannelInfo[] { COLOR_TEMPERATURE_IN_KELVIN, COLOR_TEMPERATURE_NAME }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + if (INTERFACE.equals(interfaceName)) { + Integer colorTemperatureInKelvinValue = null; + for (JsonObject state : stateList) { + if (COLOR_TEMPERATURE_IN_KELVIN.propertyName.equals(state.get("name").getAsString())) { + int value = state.get("value").getAsInt(); + // For groups take the maximum + if (colorTemperatureInKelvinValue == null) { + colorTemperatureInKelvinValue = value; + } + } + } + if (colorTemperatureInKelvinValue != null && !colorTemperatureInKelvinValue.equals(lastColorTemperature)) { + lastColorTemperature = colorTemperatureInKelvinValue; + result.needSingleUpdate = true; + } + updateState(COLOR_TEMPERATURE_IN_KELVIN.channelId, colorTemperatureInKelvinValue == null ? UnDefType.UNDEF + : new DecimalType(colorTemperatureInKelvinValue)); + } + if (INTERFACE_COLOR_PROPERTIES.equals(interfaceName)) { + String colorTemperatureNameValue = null; + for (JsonObject state : stateList) { + if (COLOR_TEMPERATURE_NAME.propertyName.equals(state.get("name").getAsString())) { + if (colorTemperatureNameValue == null) { + result.needSingleUpdate = false; + colorTemperatureNameValue = state.get("value").getAsJsonObject().get("name").getAsString(); + } + } + } + if (lastColorName == null) { + lastColorName = colorTemperatureNameValue; + } else if (colorTemperatureNameValue == null && lastColorName != null) { + colorTemperatureNameValue = lastColorName; + } + updateState(COLOR_TEMPERATURE_NAME.channelId, + colorTemperatureNameValue == null ? UnDefType.UNDEF : new StringType(colorTemperatureNameValue)); + } + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(COLOR_TEMPERATURE_IN_KELVIN.channelId)) { + // WRITING TO THIS CHANNEL DOES CURRENTLY NOT WORK, BUT WE LEAVE THE CODE FOR FUTURE USE! + if (containsCapabilityProperty(capabilties, COLOR_TEMPERATURE_IN_KELVIN.propertyName)) { + if (command instanceof DecimalType) { + int intValue = ((DecimalType) command).intValue(); + if (intValue < 1000) { + intValue = 1000; + } + if (intValue > 10000) { + intValue = 10000; + } + connection.smartHomeCommand(entityId, "setColorTemperature", "colorTemperatureInKelvin", intValue); + return true; + } + } + } + if (channelId.equals(COLOR_TEMPERATURE_NAME.channelId)) { + if (containsCapabilityProperty(capabilties, COLOR_TEMPERATURE_IN_KELVIN.propertyName)) { + if (command instanceof StringType) { + String colorTemperatureName = ((StringType) command).toFullString(); + if (StringUtils.isNotEmpty(colorTemperatureName)) { + lastColorName = colorTemperatureName; + connection.smartHomeCommand(entityId, "setColorTemperature", "colorTemperatureName", + colorTemperatureName); + return true; + } + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelUID, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java new file mode 100644 index 0000000000000..11037a893acbc --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPercentageController.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_DIMMER; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerPercentageController} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerPercentageController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.PercentageController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_PERCENTAGE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "percentage"); + + // Channel definitions + private static final ChannelInfo PERCENTAGE = new ChannelInfo("percentage" /* propertyName */ , + "percentage" /* ChannelId */, CHANNEL_TYPE_PERCENTAGE /* Channel Type */ , + ITEM_TYPE_DIMMER /* Item Type */); + + private @Nullable Integer lastPercentage; + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (PERCENTAGE.propertyName.equals(property)) { + return new ChannelInfo[] { PERCENTAGE }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + Integer percentageValue = null; + for (JsonObject state : stateList) { + if (PERCENTAGE.propertyName.equals(state.get("name").getAsString())) { + int value = state.get("value").getAsInt(); + // For groups take the maximum + if (percentageValue == null) { + percentageValue = value; + } else if (value > percentageValue) { + percentageValue = value; + } + } + } + if (percentageValue != null) { + lastPercentage = percentageValue; + } + updateState(PERCENTAGE.channelId, percentageValue == null ? UnDefType.UNDEF : new PercentType(percentageValue)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(PERCENTAGE.channelId)) { + if (containsCapabilityProperty(capabilties, PERCENTAGE.propertyName)) { + if (command.equals(IncreaseDecreaseType.INCREASE)) { + Integer lastPercentage = this.lastPercentage; + if (lastPercentage != null) { + int newValue = lastPercentage++; + if (newValue > 100) { + newValue = 100; + } + this.lastPercentage = newValue; + connection.smartHomeCommand(entityId, "setPercentage", PERCENTAGE.propertyName, newValue); + return true; + } + } else if (command.equals(IncreaseDecreaseType.DECREASE)) { + Integer lastPercentage = this.lastPercentage; + if (lastPercentage != null) { + int newValue = lastPercentage--; + if (newValue < 0) { + newValue = 0; + } + this.lastPercentage = newValue; + connection.smartHomeCommand(entityId, "setPercentage", PERCENTAGE.propertyName, newValue); + return true; + } + } else if (command.equals(OnOffType.OFF)) { + lastPercentage = 0; + connection.smartHomeCommand(entityId, "setPercentage", PERCENTAGE.propertyName, 0); + return true; + } else if (command.equals(OnOffType.ON)) { + lastPercentage = 100; + connection.smartHomeCommand(entityId, "setPercentage", PERCENTAGE.propertyName, 100); + return true; + } else if (command instanceof PercentType) { + lastPercentage = ((PercentType) command).intValue(); + connection.smartHomeCommand(entityId, "setPercentage", PERCENTAGE.propertyName, lastPercentage); + return true; + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java new file mode 100644 index 0000000000000..9ff1d0c325417 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerController.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_SWITCH; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerPowerController} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerPowerController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.PowerController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_POWER_STATE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "powerState"); + + // Channel definitions + private static final ChannelInfo POWER_STATE = new ChannelInfo("powerState" /* propertyName */ , + "powerState" /* ChannelId */, CHANNEL_TYPE_POWER_STATE /* Channel Type */ , + ITEM_TYPE_SWITCH /* Item Type */); + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (POWER_STATE.propertyName.equals(property)) { + return new ChannelInfo[] { POWER_STATE }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + Boolean powerStateValue = null; + for (JsonObject state : stateList) { + if (POWER_STATE.propertyName.equals(state.get("name").getAsString())) { + String value = state.get("value").getAsString(); + // For groups take true if all true + if ("ON".equals(value)) { + powerStateValue = true; + } else if (powerStateValue == null) { + powerStateValue = false; + } + + } + } + updateState(POWER_STATE.channelId, + powerStateValue == null ? UnDefType.UNDEF : (powerStateValue ? OnOffType.ON : OnOffType.OFF)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilities, String channelId, Command command) throws IOException { + if (channelId.equals(POWER_STATE.channelId)) { + if (containsCapabilityProperty(capabilities, POWER_STATE.propertyName)) { + if (command.equals(OnOffType.ON)) { + connection.smartHomeCommand(entityId, "turnOn"); + return true; + } else if (command.equals(OnOffType.OFF)) { + connection.smartHomeCommand(entityId, "turnOff"); + return true; + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java new file mode 100644 index 0000000000000..d9ae0c33eb86c --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerPowerLevelController.java @@ -0,0 +1,144 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_DIMMER; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerPowerLevelController} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerPowerLevelController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.PowerLevelController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_POWER_LEVEL = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "powerLevel"); + + // Channel definitions + private static final ChannelInfo POWER_LEVEL = new ChannelInfo("powerLevel" /* propertyName */ , + "powerLevel" /* ChannelId */, CHANNEL_TYPE_POWER_LEVEL /* Channel Type */ , + ITEM_TYPE_DIMMER /* Item Type */); + + private @Nullable Integer lastPowerLevel; + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (POWER_LEVEL.propertyName.equals(property)) { + return new ChannelInfo[] { POWER_LEVEL }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + Integer powerLevelValue = null; + for (JsonObject state : stateList) { + if (POWER_LEVEL.propertyName.equals(state.get("name").getAsString())) { + int value = state.get("value").getAsInt(); + // For groups take the maximum + if (powerLevelValue == null) { + powerLevelValue = value; + } else if (value > powerLevelValue) { + powerLevelValue = value; + } + } + } + if (powerLevelValue != null) { + lastPowerLevel = powerLevelValue; + } + updateState(POWER_LEVEL.channelId, + powerLevelValue == null ? UnDefType.UNDEF : new PercentType(powerLevelValue)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(POWER_LEVEL.channelId)) { + if (containsCapabilityProperty(capabilties, POWER_LEVEL.propertyName)) { + if (command.equals(IncreaseDecreaseType.INCREASE)) { + Integer lastPowerLevel = this.lastPowerLevel; + if (lastPowerLevel != null) { + int newValue = lastPowerLevel++; + if (newValue > 100) { + newValue = 100; + } + this.lastPowerLevel = newValue; + connection.smartHomeCommand(entityId, "setPowerLevel", POWER_LEVEL.propertyName, newValue); + return true; + } + } else if (command.equals(IncreaseDecreaseType.DECREASE)) { + Integer lastPowerLevel = this.lastPowerLevel; + if (lastPowerLevel != null) { + int newValue = lastPowerLevel--; + if (newValue < 0) { + newValue = 0; + } + this.lastPowerLevel = newValue; + connection.smartHomeCommand(entityId, "setPowerLevel", POWER_LEVEL.propertyName, newValue); + return true; + } + } else if (command.equals(OnOffType.OFF)) { + lastPowerLevel = 0; + connection.smartHomeCommand(entityId, "setPowerLevel", POWER_LEVEL.propertyName, 0); + return true; + } else if (command.equals(OnOffType.ON)) { + lastPowerLevel = 100; + connection.smartHomeCommand(entityId, "setPowerLevel", POWER_LEVEL.propertyName, 100); + return true; + } else if (command instanceof PercentType) { + lastPowerLevel = ((PercentType) command).intValue(); + connection.smartHomeCommand(entityId, "setPowerLevel", POWER_LEVEL.propertyName, + ((PercentType) command).floatValue() / 100); + return true; + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java new file mode 100644 index 0000000000000..adeb0da2a3666 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerSecurityPanelController.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_CONTACT; +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_STRING; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.AmazonEchoControlBindingConstants; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerSecurityPanelController} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerSecurityPanelController extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.SecurityPanelController"; + + // Channel types + private static final ChannelTypeUID CHANNEL_TYPE_ARM_STATE = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "armState"); + + private static final ChannelTypeUID CHANNEL_TYPE_BURGLARY_ALARM = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "burglaryAlarm"); + + private static final ChannelTypeUID CHANNEL_TYPE_CARBON_MONOXIDE_ALARM = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "carbonMonoxideAlarm"); + + private static final ChannelTypeUID CHANNEL_TYPE_FIRE_ALARM = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "fireAlarm"); + + private static final ChannelTypeUID CHANNEL_TYPE_WATER_ALARM = new ChannelTypeUID( + AmazonEchoControlBindingConstants.BINDING_ID, "waterAlarm"); + + // Channel definitions + private static final ChannelInfo ARM_STATE = new ChannelInfo("armState" /* propertyName */ , + "armState" /* ChannelId */, CHANNEL_TYPE_ARM_STATE /* Channel Type */ , ITEM_TYPE_STRING /* Item Type */); + + private static final ChannelInfo BURGLARY_ALARM = new ChannelInfo("burglaryAlarm" /* propertyName */ , + "burglaryAlarm" /* ChannelId */, CHANNEL_TYPE_BURGLARY_ALARM /* Channel Type */ , + ITEM_TYPE_CONTACT /* Item Type */); + + private static final ChannelInfo CARBON_MONOXIDE_ALARM = new ChannelInfo("carbonMonoxideAlarm" /* propertyName */ , + "carbonMonoxideAlarm" /* ChannelId */, CHANNEL_TYPE_CARBON_MONOXIDE_ALARM /* Channel Type */ , + ITEM_TYPE_CONTACT /* Item Type */); + + private static final ChannelInfo FIRE_ALARM = new ChannelInfo("fireAlarm" /* propertyName */ , + "fireAlarm" /* ChannelId */, CHANNEL_TYPE_FIRE_ALARM /* Channel Type */ , + ITEM_TYPE_CONTACT /* Item Type */); + + private static final ChannelInfo WATER_ALARM = new ChannelInfo("waterAlarm" /* propertyName */ , + "waterAlarm" /* ChannelId */, CHANNEL_TYPE_WATER_ALARM /* Channel Type */ , + ITEM_TYPE_CONTACT /* Item Type */); + + private ChannelInfo[] getAlarmChannels() { + return new ChannelInfo[] { BURGLARY_ALARM, CARBON_MONOXIDE_ALARM, FIRE_ALARM, WATER_ALARM }; + } + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (ARM_STATE.propertyName.equals(property)) { + return new ChannelInfo[] { ARM_STATE }; + } + for (ChannelInfo channelInfo : getAlarmChannels()) { + if (channelInfo.propertyName.equals(property)) { + return new ChannelInfo[] { channelInfo }; + } + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + String armStateValue = null; + Boolean burglaryAlarmValue = null; + Boolean carbonMonoxideAlarmValue = null; + Boolean fireAlarmValue = null; + Boolean waterAlarmValue = null; + for (JsonObject state : stateList) { + if (ARM_STATE.propertyName.equals(state.get("name").getAsString())) { + if (armStateValue == null) { + armStateValue = state.get("value").getAsString(); + } + } else if (BURGLARY_ALARM.propertyName.equals(state.get("name").getAsString())) { + if (burglaryAlarmValue == null) { + burglaryAlarmValue = "ALARM".equals(state.get("value").getAsString()); + } + } else if (CARBON_MONOXIDE_ALARM.propertyName.equals(state.get("name").getAsString())) { + if (carbonMonoxideAlarmValue == null) { + carbonMonoxideAlarmValue = "ALARM".equals(state.get("value").getAsString()); + } + } else if (FIRE_ALARM.propertyName.equals(state.get("name").getAsString())) { + if (fireAlarmValue == null) { + fireAlarmValue = "ALARM".equals(state.get("value").getAsString()); + } + } else if (WATER_ALARM.propertyName.equals(state.get("name").getAsString())) { + if (waterAlarmValue == null) { + waterAlarmValue = "ALARM".equals(state.get("value").getAsString()); + } + } + } + updateState(ARM_STATE.channelId, armStateValue == null ? UnDefType.UNDEF : new StringType(armStateValue)); + updateState(BURGLARY_ALARM.channelId, burglaryAlarmValue == null ? UnDefType.UNDEF + : (burglaryAlarmValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + updateState(CARBON_MONOXIDE_ALARM.channelId, carbonMonoxideAlarmValue == null ? UnDefType.UNDEF + : (carbonMonoxideAlarmValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + updateState(FIRE_ALARM.channelId, fireAlarmValue == null ? UnDefType.UNDEF + : (fireAlarmValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + updateState(WATER_ALARM.channelId, waterAlarmValue == null ? UnDefType.UNDEF + : (waterAlarmValue ? OpenClosedType.CLOSED : OpenClosedType.OPEN)); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + if (channelId.equals(ARM_STATE.channelId)) { + if (containsCapabilityProperty(capabilties, ARM_STATE.propertyName)) { + if (command instanceof StringType) { + String armStateValue = ((StringType) command).toFullString(); + if (StringUtils.isNotEmpty(armStateValue)) { + connection.smartHomeCommand(entityId, "controlSecurityPanel", ARM_STATE.propertyName, + armStateValue); + return true; + } + } + } + } + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelUID, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java new file mode 100644 index 0000000000000..5610ef858e259 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/HandlerTemperatureSensor.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.CHANNEL_TYPE_TEMPERATURE; +import static org.openhab.binding.amazonechocontrol.internal.smarthome.Constants.ITEM_TYPE_NUMBER_TEMPERATURE; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.library.unit.ImperialUnits; +import org.eclipse.smarthome.core.library.unit.SIUnits; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.amazonechocontrol.internal.Connection; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; + +import com.google.gson.JsonObject; + +/** + * The {@link HandlerTemperatureSensor} is responsible for the Alexa.PowerControllerInterface + * + * @author Lukas Knoeller - Initial contribution + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class HandlerTemperatureSensor extends HandlerBase { + // Interface + public static final String INTERFACE = "Alexa.TemperatureSensor"; + // Channel definitions + private static final ChannelInfo TEMPERATURE = new ChannelInfo("temperature" /* propertyName */ , + "temperature" /* ChannelId */, CHANNEL_TYPE_TEMPERATURE /* Channel Type */ , + ITEM_TYPE_NUMBER_TEMPERATURE /* Item Type */); + + @Override + public String[] getSupportedInterface() { + return new String[] { INTERFACE }; + } + + @Override + protected ChannelInfo @Nullable [] findChannelInfos(SmartHomeCapability capability, String property) { + if (TEMPERATURE.propertyName.equals(property)) { + return new ChannelInfo[] { TEMPERATURE }; + } + return null; + } + + @Override + public void updateChannels(String interfaceName, List stateList, UpdateChannelResult result) { + QuantityType temperatureValue = null; + for (JsonObject state : stateList) { + if (TEMPERATURE.propertyName.equals(state.get("name").getAsString())) { + JsonObject value = state.get("value").getAsJsonObject(); + // For groups take the first + if (temperatureValue == null) { + int temperature = value.get("value").getAsInt(); + String scale = value.get("scale").getAsString(); + if ("CELSIUS".equals(scale)) { + temperatureValue = new QuantityType(temperature, SIUnits.CELSIUS); + } else { + temperatureValue = new QuantityType(temperature, ImperialUnits.FAHRENHEIT); + } + } + } + } + updateState(TEMPERATURE.channelId, temperatureValue == null ? UnDefType.UNDEF : temperatureValue); + } + + @Override + public boolean handleCommand(Connection connection, SmartHomeDevice shd, String entityId, + SmartHomeCapability[] capabilties, String channelId, Command command) throws IOException { + return false; + } + + @Override + public @Nullable StateDescription findStateDescription(String channelId, StateDescription originalStateDescription, + @Nullable Locale locale) { + return null; + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java new file mode 100644 index 0000000000000..40dab399d7ae6 --- /dev/null +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/java/org/openhab/binding/amazonechocontrol/internal/smarthome/SmartHomeDeviceStateGroupUpdateCalculator.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.amazonechocontrol.internal.smarthome; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapabilities.SmartHomeCapability; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.DriverIdentity; +import org.openhab.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevices.SmartHomeDevice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles the update interval calculation + * + * @author Michael Geramb - Initial contribution + */ +@NonNullByDefault +public class SmartHomeDeviceStateGroupUpdateCalculator { + private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceStateGroupUpdateCalculator.class); + + private static final Integer UPDATE_INTERVAL_PRIVATE_SKILLS_IN_SECONDS = 10; + private static final Integer UPDATE_INTERVAL_PRIVATE_SKILLS_IN_SECONDS_TRACE = 600; + private static final Integer UPDATE_INTERVAL_ACOUSTIC_EVENTS_IN_SECONDS = 10; + private Integer updateIntervalAmazonInSeconds; + private Integer updateIntervalSkillsInSeconds; + + private static class UpdateGroup { + private final int intervalInSeconds; + private Date lastUpdated; + + public UpdateGroup(int intervalInSeconds) { + this.intervalInSeconds = intervalInSeconds; + this.lastUpdated = new Date(0); + } + } + + private final Map updateGroups = new HashMap<>(); + + public SmartHomeDeviceStateGroupUpdateCalculator(int updateIntervalAmazonInSeconds, + int updateIntervalSkillsInSeconds) { + this.updateIntervalAmazonInSeconds = updateIntervalAmazonInSeconds; + this.updateIntervalSkillsInSeconds = updateIntervalSkillsInSeconds; + } + + private Integer getUpdateIntervalInSeconds(SmartHomeDevice shd) { + Integer updateIntervalInSeconds = shd.updateIntervalInSeconds; + if (updateIntervalInSeconds != null) { + return updateIntervalInSeconds; + } + SmartHomeCapability[] capabilities = shd.capabilities; + if (capabilities != null) { + for (SmartHomeCapability capability : capabilities) { + if (capability != null && HandlerAcousticEventSensor.INTERFACE.equals(capability.interfaceName)) { + updateIntervalInSeconds = UPDATE_INTERVAL_ACOUSTIC_EVENTS_IN_SECONDS; + break; + } + } + } + if (updateIntervalInSeconds == null) { + if ("openHAB".equalsIgnoreCase(shd.manufacturerName) + || StringUtils.startsWithIgnoreCase(shd.manufacturerName, "ioBroker")) { + // OpenHAB or ioBroker skill + if (logger.isTraceEnabled()) { + updateIntervalInSeconds = UPDATE_INTERVAL_PRIVATE_SKILLS_IN_SECONDS_TRACE; + } else { + updateIntervalInSeconds = UPDATE_INTERVAL_PRIVATE_SKILLS_IN_SECONDS; + } + } else { + boolean isSkillDevice = false; + DriverIdentity driverIdentity = shd.driverIdentity; + isSkillDevice = driverIdentity != null && "SKILL".equals(driverIdentity.namespace); + if (isSkillDevice) { + updateIntervalInSeconds = updateIntervalSkillsInSeconds; + } else { + updateIntervalInSeconds = updateIntervalAmazonInSeconds; + } + } + } + shd.updateIntervalInSeconds = updateIntervalInSeconds; + return updateIntervalInSeconds; + } + + public void removeDevicesWithNoUpdate(List devices) { + Date updateTimeStamp = new Date(); + // check if new group is needed + boolean syncAllGroups = false; + for (SmartHomeDevice device : devices) { + int updateIntervalInSeconds = getUpdateIntervalInSeconds(device); + if (!updateGroups.containsKey(updateIntervalInSeconds)) { + UpdateGroup newGroup = new UpdateGroup(updateIntervalInSeconds); + updateGroups.put(updateIntervalInSeconds, newGroup); + syncAllGroups = true; + } + } + // check which groups needs an update + Set groupsToUpdate = new HashSet(); + for (UpdateGroup group : updateGroups.values()) { + long millisecondsSinceLastUpdate = updateTimeStamp.getTime() - group.lastUpdated.getTime(); + if (syncAllGroups || millisecondsSinceLastUpdate >= group.intervalInSeconds * 1000) { + group.lastUpdated = updateTimeStamp; + groupsToUpdate.add(group.intervalInSeconds); + } + } + // remove unused devices + for (int i = devices.size() - 1; i >= 0; i--) { + if (!groupsToUpdate.contains(getUpdateIntervalInSeconds(devices.get(i)))) { + devices.remove(i); + } + } + } +} diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/binding/binding.xml index 6036baea74da3..a51d4eadc0930 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/binding/binding.xml @@ -6,6 +6,6 @@ Amazon Echo Control Binding Binding to control Amazon Echo devices (Alexa). This binding enables openHAB to control the volume, playing state, bluetooth connection of your amazon echo devices or allow to use it as TTS device. - Michael Geramb + Michael Geramb, Lukas Knoeller diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties index 699cd51b94144..1661f411aa410 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/i18n/amazonechocontrol_de.properties @@ -6,6 +6,13 @@ binding.amazonechocontrol.description = Binding zum Steuern von Amazon Echo (Ale thing-type.amazonechocontrol.account.label = Amazon Konto thing-type.amazonechocontrol.account.description = Amazon Konto bei dem die Amazon Echo Geräte registriert sind. +thing-type.amazonechocontrol.account.config-description.discoverSmartHome.description = Definiert ob openHAB Geräte die mit Amazon Echo verbunden sind, gefunden werden sollen. +thing-type.amazonechocontrol.account.config-description.discoverSmartHome.option.0 = Nicht finden +thing-type.amazonechocontrol.account.config-description.discoverSmartHome.option.1 = Direkt verbundene +thing-type.amazonechocontrol.account.config-description.discoverSmartHome.option.2 = Direkt und über Skill verbundene +thing-type.amazonechocontrol.account.config-description.pollingIntervalSmartHomeAlexa.description = Definiert das Intervall in Sekunden, wie häufig OpenHAB den Status der mit Alexa verbundenen Geräte abfragt. +thing-type.amazonechocontrol.account.config-description.pollingIntervalSmartSkills.description = Definiert das Intervall in Sekunden, wie häufig OpenHAB den Status der über Skill Geräte abfragt. +thing-type.amazonechocontrol.account.config-description.discoverOpenHabSmartHomeDevices.description = Definiert ob auch Geräte die über den openHAB Skill verbunden sind, gefunden werden sollen. Diese Option ist nur für Entwicklungs- und Testzwecke. thing-type.amazonechocontrol.echo.label = Amazon Echo thing-type.amazonechocontrol.echo.description = Amazon Echo Gerät (Amazon Echo, Amazon Echo Dot, Amazon Echo Plus...) @@ -34,6 +41,19 @@ thing-type.config.wha.echoshow.serialNumber.description = Die Seriennummer des G thing-type.amazonechocontrol.flashbriefingprofile.label = Tägliche Zusammenfassungsprofile thing-type.amazonechocontrol.flashbriefingprofile.description = Speichert und läd eine Tägliches Zusammenfassungskonfiguration +thing-type.amazonechocontrol.smartHomeDevice.label = Smart Home Gerät +thing-type.amazonechocontrol.smartHomeDevice.description = Ein mit Alexa verbundenes Smart Home Gerät + +thing-type.amazonechocontrol.smartHomeDevice.id.label = Geräte Id +thing-type.amazonechocontrol.smartHomeDevice.id.description = Die Id des Gerätes (Bitte die Suchfunktion benutzen, um ein konfiguriertes Thing zu bekommen) + +thing-type.amazonechocontrol.smartHomeDeviceGroup.label = Smart Home Geräte Gruppe +thing-type.amazonechocontrol.smartHomeDeviceGroup.description = Eine Gruppe von Smart Home Geräten + +thing-type.amazonechocontrol.smartHomeDevice.id.label = Gruppen Id +thing-type.amazonechocontrol.smartHomeDevice.id.description = Die Id der Gerätegruppe (Bitte die Suchfunktion benutzen, um ein konfiguriertes Thing zu bekommen) + + # channel types channel-type.amazonechocontrol.bluetoothDeviceName.label = Bluetooth Gerät channel-type.amazonechocontrol.bluetoothDeviceName.description = Verbundenes Bluetoothgerät @@ -169,3 +189,44 @@ channel-type.amazonechocontrol.playCommand.option.GoodMorning = Guten Morgen channel-type.amazonechocontrol.playCommand.option.SingASong = Lied channel-type.amazonechocontrol.playCommand.option.TellStory = Geschichte channel-type.amazonechocontrol.playCommand.option.TellStory = Zusammenfassung + +channel-type-amazonechocontrol.colorName.label = Lichtfarbe Name +channel-type-amazonechocontrol.colorName.description = Farbe der smarten Lampe +channel-type-amazonechocontrol.colorName.option.red = Rot +channel-type-amazonechocontrol.colorName.option.crimson = Purpur +channel-type-amazonechocontrol.colorName.option.salmon = Lachs +channel-type-amazonechocontrol.colorName.option.orange = Orange +channel-type-amazonechocontrol.colorName.option.gold = Gold +channel-type-amazonechocontrol.colorName.option.yellow = Gelb +channel-type-amazonechocontrol.colorName.option.green = Grün +channel-type-amazonechocontrol.colorName.option.turquoise = Türkis +channel-type-amazonechocontrol.colorName.option.cyan = Cyan +channel-type-amazonechocontrol.colorName.option.sky_blue = Himmelblau +channel-type-amazonechocontrol.colorName.option.blue = Blau +channel-type-amazonechocontrol.colorName.option.purple = Lila +channel-type-amazonechocontrol.colorName.option.magenta = Magenta +channel-type-amazonechocontrol.colorName.option.pink = Pink +channel-type-amazonechocontrol.colorName.option.lavender = Lavendel + +channel-type-amazonechocontrol.colorTemperatureName.label = Weißton Name +channel-type-amazonechocontrol.colorTemperatureName.description = Weißtemperatur der Lampe am Amazon Echo +channel-type-amazonechocontrol.colorTemperatureName.option.warm_white = Warmweiß +channel-type-amazonechocontrol.colorTemperatureName.option.soft_white = Sanftes Weiß +channel-type-amazonechocontrol.colorTemperatureName.option.white = Weiß +channel-type-amazonechocontrol.colorTemperatureName.option.daylight_white = Tageslicht +channel-type-amazonechocontrol.colorTemperatureName.option.cool_white = Kaltweiß + +channel-type-amazonechocontrol.brightness.label = Helligkeit +channel-type-amazonechocontrol.brightness.description = Helligkeit der Lampe + +channel-type-amazonechocontrol.powerState.label = Zustand +channel-type-amazonechocontrol.powerState.description = Zustand (Ein/Aus) + +channel-type-amazonechocontrol.armState.label = Zustand +channel-type-amazonechocontrol.armState.description = Zustand (Active/Stay/Night/Deactivated) + +channel-type-amazonechocontrol.armState.option.ARMED_AWAY = Aktiv +channel-type-amazonechocontrol.armState.option.ARMED_STAY = Anwesend +channel-type-amazonechocontrol.armState.option.ARMED_NIGHT = Nacht +channel-type-amazonechocontrol.armState.option.DISARMED = Deativiert + diff --git a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml index 0618a2b93f8ce..4758bb3d74a6b 100644 --- a/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.amazonechocontrol/src/main/resources/ESH-INF/thing/thing-types.xml @@ -1,471 +1,530 @@ - - - - - Amazon Account where the amazon echo devices are registered. - - - - - - - - - - Amazon Echo device (Amazon Echo, Amazon Echo Dot, Amazon Echo Plus...) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - serialNumber - - - - The serial number of the device from the Alexa app - - - - - - - - - Amazon Echo Spot device - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - serialNumber - - - - The serial number of the device from the Alexa app - - - - - - - - - Amazon Echo Show device - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - serialNumber - - - - The serial number of the device from the Alexa app - - - - - - - - - Amazon Multiroom Music - - - - - - - - - - - - - - - - - - serialNumber - - - - The serial number of the device from the Alexa app - - - - - - - - - - Store and load a flash briefing configuration - - - - - - - - Switch - - Save the current flash briefing configuration (Write only) - - - Switch - - Activate this flash briefing configuration - - - String - - Plays the briefing on the device (serial number or name, write only) - - - String - - Connected bluetooth device - - - - String - - Id of the radio station - - - String - - Speak the reminder and send a notification to the Alexa app - - - String - - The command which must be spoken to active the routing without the preceding "Alexa," (Write Only) - - - - String - - Plays an alarm sound - - - String - - Id of the amazon music track - - - Switch - - Amazon Music turned on - - - String - - Amazon Music play list id (Write only, no current state) - - - String - - Id of the playlist which was started with openHAB - - - String - - Name of music provider - - - - String - - MAC-Address of the bluetooth connected device - - - String - - Url of the album image or radio station logo - - - - String - - Title - - - - String - - Subtitle 1 - - - - String - - Subtitle 2 - - - - Switch - - Radio turned on - - - Switch - - Connect to last used device - - - Switch - - Loop - - - Switch - - Shuffle play - - - Player - - Music Player - - - Dimmer - - Volume of the sound - - - Number - - Equalizer Treble - - - - Number - - Equalizer Midrange - - - - Number - - Equalizer Bass - - - - String - - Music provider - - - String - - Voice command as text. E.g. 'Yesterday from the Beatles' (Write only) - - - String - - Sends a message to the Echo devices (Write only). - - - String - - Display the announcement message on the display (Write only). See in the tutorial section of the binding - description to learn how it's possible to set the title and turn off the sound. - - - String - - Speak the text (Write only). It is possible to use plain text or SSML: <speak>I want to tell you a - secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect>.Can you believe - it?</speak> - - - Dimmer - - Volume of the Speak channel. If 0, the current volume will be used. - - - String - - Last voice command spoken to the device. Writing to the channel starts voice output. - - - - Dimmer - - Media progress in percent - - - - Number:Time - - Media play time - - - - Number:Time - - Media length - - - - String - - Start information (Write only) - - - - - - - - - - - - - Dimmer - - Notification Volume - - - - Switch - - Ascending alarm up to the configured volume - - - - DateTime - - Next Reminder - - - - DateTime - - Next alarm - - - - DateTime - - Next music alarm - - - - DateTime - - Next timer - - - + + + + + Amazon Account where the amazon echo devices are registered. + + + + + + + Defines which devices shall be discovered. + + + + + + + true + 0 + + + + + Defines the time in seconds for openHAB to pull the + state of the directly connected devices. The minimum + is 10 seconds. + + 30 + + + + + Defines the time in seconds for openHAB to pull the + state of the over a skill connected devices. The + minimum is 60 seconds. + + 120 + + + + + + + + + Amazon Echo device (Amazon Echo, Amazon Echo Dot, Amazon Echo Plus...) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + serialNumber + + + + The serial number of the device from the Alexa app + + + + + + + + + Amazon Echo Spot device + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + serialNumber + + + + The serial number of the device from the Alexa app + + + + + + + + + Amazon Echo Show device + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + serialNumber + + + + The serial number of the device from the Alexa app + + + + + + + + + Amazon Multiroom Music + + + + + + + + + + + + + + + + + + serialNumber + + + + The serial number of the device from the Alexa app + + + + + + + + + Store and load a flash briefing configuration + + + + + + + + + + + + Smart home device connected to Alexa + id + + + + The id of the device (Please use the discover function to get a configured Thing) + + + + + + + + + Group of smart home devices in your amazon account + id + + + + The id of the device group (Please use the discover function to get a configured Thing) + + + + + Switch + + Save the current flash briefing configuration (Write only) + + + Switch + + Activate this flash briefing configuration + + + String + + Plays the briefing on the device (serial number or name, write only) + + + String + + Connected bluetooth device + + + + String + + Id of the radio station + + + String + + Speak the reminder and send a notification to the Alexa app + + + String + + The command which must be spoken to active the routing without the preceding "Alexa," (Write Only) + + + + String + + Plays an alarm sound + + + String + + Id of the amazon music track + + + Switch + + Amazon Music turned on + + + String + + Amazon Music play list id (Write only, no current state) + + + String + + Id of the playlist which was started with openHAB + + + String + + Name of music provider + + + + String + + MAC-Address of the bluetooth connected device + + + String + + Url of the album image or radio station logo + + + + String + + Title + + + + String + + Subtitle 1 + + + + String + + Subtitle 2 + + + + Switch + + Radio turned on + + + Switch + + Connect to last used device + + + Switch + + Loop + + + Switch + + Shuffle play + + + Player + + Music Player + + + Dimmer + + Volume of the sound + + + Number + + Equalizer Treble + + + + Number + + Equalizer Midrange + + + + Number + + Equalizer Bass + + + + String + + Music provider + + + String + + Voice command as text. E.g. 'Yesterday from the Beatles' (Write only) + + + String + + Sends a message to the Echo devices (Write only). + + + String + + Display the announcement message on the display (Write only). See in the tutorial section of the binding + description to learn how it's possible to set the title and turn off the sound. + + + String + + Speak the text (Write only). It is possible to use plain text or SSML: <speak>I want to tell you a + secret.<amazon:effect name="whispered">I am not a real human.</amazon:effect>.Can you believe + it?</speak> + + + Dimmer + + Volume of the Speak channel. If 0, the current volume will be used. + + + String + + Last voice command spoken to the device. Writing to the channel starts voice output. + + + + Dimmer + + Media progress in percent + + + + Number:Time + + Media play time + + + + Number:Time + + Media length + + + + String + + Start information (Write only) + + + + + + + + + + + + + Dimmer + + Notification Volume + + + + Switch + + Ascending alarm up to the configured volume + + + + DateTime + + Next Reminder + + + + DateTime + + Next alarm + + + + DateTime + + Next music alarm + + + + DateTime + + Next timer + + +