From cc983ba5a772c2bd30e3c7219dc19649e4d1e7ee Mon Sep 17 00:00:00 2001 From: Antoine Besnard Date: Tue, 8 Nov 2016 10:48:53 +0100 Subject: [PATCH] Refactoring of the binding to allow more parameters and models. Signed-off-by: Antoine Besnard --- .../MieleMDNSDiscoveryParticipant.xml | 2 +- .../ESH-INF/binding/binding.xml | 2 +- .../ESH-INF/config/parameters.xml | 188 +++++++ .../ESH-INF/thing/thing-types.xml | 184 ------- .../META-INF/MANIFEST.MF | 5 +- .../OSGI-INF/AVRChannelTypeProvider.xml | 18 + .../OSGI-INF/AVRThingTypeProvider.xml | 19 + .../OSGI-INF/PioneerAvrDiscovery.xml | 1 + .../OSGI-INF/PioneerAvrHandlerFactory.xml | 1 + .../org.openhab.binding.pioneeravr/README.md | 93 ++-- .../org.openhab.binding.pioneeravr/about.html | 2 +- .../PioneerAvrBindingConstants.java | 71 ++- .../channeltype/AVRChannelTypeProvider.java | 271 ++++++++++ .../channeltype/ChannelDefinitionFactory.java | 101 ++++ .../PioneerAvrDiscoveryParticipant.java | 92 +++- .../internal/handler/AbstractAvrHandler.java | 333 ------------- .../handler/AvrConnectionHandler.java | 346 +++++++++++++ .../internal/handler/AvrHandler.java | 470 ++++++++++++++++++ .../internal/handler/AvrHandlerFactory.java | 33 +- .../internal/handler/IpAvrHandler.java | 36 -- .../internal/handler/SerialAvrHandler.java | 35 -- .../internal/models/AbstractModel.java | 94 ++++ .../internal/models/ConfigurableModel.java | 68 +++ .../pioneeravr/internal/models/VSX1021.java | 37 ++ .../pioneeravr/internal/models/VSX1120.java | 37 ++ .../pioneeravr/internal/models/VSX1122.java | 37 ++ .../pioneeravr/internal/models/VSX921.java | 37 ++ .../models/properties/ModelProperties.java | 121 +++++ .../properties/ModelPropertiesImpl.java | 309 ++++++++++++ .../internal/protocol/DisplayInformation.java | 1 - .../protocol/ParameterizedCommand.java | 16 +- .../protocol/RequestResponseFactory.java | 25 +- .../internal/protocol/Response.java | 49 +- .../internal/protocol/SimpleCommand.java | 41 +- .../protocol/StreamAvrConnection.java | 439 +++++++++------- .../internal/protocol/ip/IpAvrConnection.java | 18 +- .../protocol/serial/SerialAvrConnection.java | 15 +- .../thingtype/AVRThingTypeProvider.java | 87 ++++ .../internal/thingtype/ThingTypeManager.java | 46 ++ .../pioneeravr/internal/util/Pair.java | 40 ++ .../pioneeravr/protocol/AvrCommand.java | 20 +- .../pioneeravr/protocol/AvrConnection.java | 107 ++-- .../pioneeravr/protocol/AvrResponse.java | 9 +- .../protocol/event/AvrConnectionListener.java | 36 ++ .../event/AvrDisconnectionListener.java | 26 - ...onEvent.java => AvrNotificationEvent.java} | 20 +- ...ener.java => AvrNotificationListener.java} | 4 +- .../protocol/event/AvrStatusUpdateEvent.java | 36 -- .../protocol/utils/VolumeConverter.java | 135 ++++- 49 files changed, 3108 insertions(+), 1105 deletions(-) create mode 100644 addons/binding/org.openhab.binding.pioneeravr/ESH-INF/config/parameters.xml delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/ESH-INF/thing/thing-types.xml create mode 100644 addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRChannelTypeProvider.xml create mode 100644 addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRThingTypeProvider.xml create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/AVRChannelTypeProvider.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/ChannelDefinitionFactory.java delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AbstractAvrHandler.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrConnectionHandler.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandler.java delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/IpAvrHandler.java delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/SerialAvrHandler.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/AbstractModel.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/ConfigurableModel.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1021.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1120.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1122.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX921.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelProperties.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelPropertiesImpl.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/AVRThingTypeProvider.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/ThingTypeManager.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/util/Pair.java create mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrConnectionListener.java delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionListener.java rename addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/{AvrDisconnectionEvent.java => AvrNotificationEvent.java} (55%) rename addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/{AvrUpdateListener.java => AvrNotificationListener.java} (84%) delete mode 100644 addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrStatusUpdateEvent.java diff --git a/addons/binding/org.openhab.binding.miele/OSGI-INF/MieleMDNSDiscoveryParticipant.xml b/addons/binding/org.openhab.binding.miele/OSGI-INF/MieleMDNSDiscoveryParticipant.xml index 041556a9cd370..3d1a8a4ef6562 100644 --- a/addons/binding/org.openhab.binding.miele/OSGI-INF/MieleMDNSDiscoveryParticipant.xml +++ b/addons/binding/org.openhab.binding.miele/OSGI-INF/MieleMDNSDiscoveryParticipant.xml @@ -17,5 +17,5 @@ - + diff --git a/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/binding/binding.xml b/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/binding/binding.xml index 59748c822baa5..d1da5a167b739 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/binding/binding.xml +++ b/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/binding/binding.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://eclipse.org/smarthome/schemas/binding/v1.0.0 http://eclipse.org/smarthome/schemas/binding-1.0.0.xsd"> PioneerAvr Binding - A binding for Pioneer AVRs. Compatible models: SC-57, SC-LX85, SC-55, SC-1526, SC-LX75, VSX-53, VSX-1326, VSX-LX55, VSX-2021, VSA-LX55, VSX-52, VSX-1126, VSX-1121, VSX-51, VSX-1021, VSX-1026, VSA-1021, VSX-50, VSX-926, VSX-921, VSA-921. Other models may work with some odd behaviors. + A binding for Pioneer AVRs. Antoine Besnard diff --git a/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/config/parameters.xml b/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/config/parameters.xml new file mode 100644 index 0000000000000..fb57c9f832942 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/config/parameters.xml @@ -0,0 +1,188 @@ + + + + + + + + IP configuration + + + + true + Configuration to use Serial connection instead of IP. + + + + General configuration. + + + + network-address + + The address of the Pioneer AVR to control. + + + 23 + + The TCP port number used to connect to the AVR. + + + + false + + If the serial connection has to be used instead of IP. If selected, the IP connection parameters are not used. + + + + The Serial port name to use to connect to the AVR. + + + + 1 + + Below parameters for zones that are not supported will be ignored. + + + + true + + Tell if the setVolume command should be used to set the volume on the AVR. All AVRs does not support this command. If set and nothing happen when the volume is changed, disable it. When disable, the volume is set in Burst Mode. + + + + 10 + ms + + The delay (in ms) between two messages send to the AVR in burst mode. If the delay is too short, some messages may be missed by the AVR and the volume level will not be accurate. If too high, the volume will be longer to adjust to the requested value. + + + + true + + Disable/Enable the burst mode. Should be disabled only if no valid value of Burst message delay can be found. If Disabled, the volume change may be slow. Should only be used in last resort. + + + + -80 + dB + + Minimum volume (in dB). + + + + 12 + dB + + Maximum volume (in dB). + + + + 0.5 + dB + + Volume step (in dB) when the volume Up/Down touch of the remote is pressed. + + + + -80 + dB + + Minimum volume (in dB). + + + + 0 + dB + + Maximum volume (in dB). + + + + 1 + dB + + Volume step (in dB) when the volume Up/Down touch of the remote is pressed. + + + + -80 + dB + + Minimum volume (in dB). + + + + 0 + dB + + Maximum volume (in dB). + + + + 1 + dB + + Volume step (in dB) when the volume Up/Down touch of the remote is pressed. + + + + + -80 + dB + + Minimum volume (in dB). + + + + 0 + dB + + Maximum volume (in dB). + + + + 1 + dB + + Volume step (in dB) when the volume Up/Down touch of the remote is pressed. + + + + + + + + IP configuration + + + + true + Configuration to use Serial connection instead of IP. + + + + network-address + + The address of the Pioneer AVR to control. + + + 23 + + The TCP port number used to connect to the AVR. + + + + false + + If the serial connection has to be used instead of IP. If selected, the IP connection parameters are not used. + + + + The Serial port name to use to connect to the AVR. + + + diff --git a/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/thing/thing-types.xml b/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/thing/thing-types.xml deleted file mode 100644 index accd061aa8c8e..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/ESH-INF/thing/thing-types.xml +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - Control a Pioneer AVR over IP - - - - - - - - - - - network-address - - The address of the Pioneer AVR to control. - - - 8102 - - The TCP port number used to connect to the AVR. - - - - - - - - Control a Pioneer AVR over IP for models that are not officially supported. You may experience some odd behaviors. - - - - - - - - - - - network-address - - The address of the Pioneer AVR to control. - - - 8102 - - The TCP port number used to connect to the AVR. - - - - - - - - Control a Pioneer AVR over a Serial port (RS-232). - - - - - - - - - - - - The Serial port name to use to connect to the AVR. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Switch - - Power ON/OFF the AVR - - - - Dimmer - - Increase/Decrease the volume (%) and mute/un-mute - SoundVolume - - - - - Number - - Set the volume level (dB) - SoundVolume - - - - - Switch - - Enable/Disable Mute on the AVR - - - - String - - Select the input source of the AVR - - - - - - - - - - - - - - - - - - - - - - - - - - - - - String - - Display the information displayed on the AVR front screen - - - - diff --git a/addons/binding/org.openhab.binding.pioneeravr/META-INF/MANIFEST.MF b/addons/binding/org.openhab.binding.pioneeravr/META-INF/MANIFEST.MF index 1f7127332387e..7d337d4ba6f25 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/META-INF/MANIFEST.MF +++ b/addons/binding/org.openhab.binding.pioneeravr/META-INF/MANIFEST.MF @@ -12,10 +12,10 @@ Export-Package: org.openhab.binding.pioneeravr.protocol.event, org.openhab.binding.pioneeravr.protocol.states, org.openhab.binding.pioneeravr.protocol.utils -Import-Package: - com.google.common.base, +Import-Package: com.google.common.base, com.google.common.collect, gnu.io;resolution:=optional, + org.apache.commons.collections, org.apache.commons.lang, org.eclipse.jdt.annotation;resolution:=optional, org.eclipse.smarthome.config.core, @@ -24,6 +24,7 @@ Import-Package: org.eclipse.smarthome.core.library.types, org.eclipse.smarthome.core.thing, org.eclipse.smarthome.core.thing.binding, + org.eclipse.smarthome.core.thing.type, org.eclipse.smarthome.core.types, org.jupnp.model.meta, org.jupnp.model.types, diff --git a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRChannelTypeProvider.xml b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRChannelTypeProvider.xml new file mode 100644 index 0000000000000..d9c132a4b32ac --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRChannelTypeProvider.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRThingTypeProvider.xml b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRThingTypeProvider.xml new file mode 100644 index 0000000000000..a71e9172d5fdc --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/AVRThingTypeProvider.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrDiscovery.xml b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrDiscovery.xml index 255e79f890e64..85ef49f07e305 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrDiscovery.xml +++ b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrDiscovery.xml @@ -14,4 +14,5 @@ + diff --git a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrHandlerFactory.xml b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrHandlerFactory.xml index e553d46d6f75e..0895a9666664d 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrHandlerFactory.xml +++ b/addons/binding/org.openhab.binding.pioneeravr/OSGI-INF/PioneerAvrHandlerFactory.xml @@ -14,4 +14,5 @@ + diff --git a/addons/binding/org.openhab.binding.pioneeravr/README.md b/addons/binding/org.openhab.binding.pioneeravr/README.md index 9c11f0e8f2e6d..ef6abe3e0fad9 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/README.md +++ b/addons/binding/org.openhab.binding.pioneeravr/README.md @@ -5,49 +5,84 @@ The binding can auto-discover the Pioneer AVRs present on your local network. The auto-discovery is enabled by default. To disable it, you can create a file in the services directory called pioneeravr.cfg with the following content: ``` -#Put your configuration here org.openhab.pioneeravr:enableAutoDiscovery=false ``` This configuration parameter only control the PioneerAVR auto-discovery process, not the openHAB auto-discovery. Moreover, if the openHAB auto-discovery is disabled, the PioneerAVR auto-discovery is disabled too. -##Thing configuration +## Thing configuration In the things folder, create a file called pioneeravr.things (or any other name) and configure your AVRs inside. -The binding can control AVRs through the local network (ipAvr/ipAvrUnsupported thing type) or through a Serial connection (serialAvr) if the AVR is directly connected to your computer. +The binding can control AVRs through the local network or through a Serial connection if the AVR is directly connected to your computer. -Configuration of ipAvr/ipAvrUnsupported: +AVR models/thing-types that are supported by the binding: -* address: the hostname/ipAddress of the AVR on the local network. (mandatory) -* tcpPort: the port number to use to connect to the AVR. (optional, default to 23) +* **VSX-1022** +* **VSX-1021** +* **VSX-1020** +* **VSX-921** +The thing-type ConfigurablePioneerAVR should be used to test unsupported AVRs. Once the parameters are found for the model, please contact the binding maintainer to add the model in the supported list. -Configuration of serialAvr: +The following parameters are available for all thing-types: -* serialPort: the name of the serial port on your computer. (mandatory) +* **ipAddress**: the hostname/ipAddress of the AVR on the local network. (mandatory if useSerial is false) +* **tcpPort**: the port number to use to connect to the AVR. If default value does not work, you may try 8102. (optional, default to 23) +* **useSerial**: use the serial port to communicate with the AVR instead of IP. (optional, default to false) +* **serialPort**: the name of the serial port to use on the computer. (mandatory if userSerial is true) -Example: +The ConfigurablePioneerAvr thing-type also defines the below parameters: +* **nbZones**: defines the number of zones supported by the AVR. (default to 1) +* **setVolumeCommandEnabled**: enable/disable the use of the SetVolume command. Not all AVR are compatible with this command. If nothing happens when volume is changed, disable it. (default to true). +* **burstMessageDelay**: the delay in milliseconds between 2 messages in burst mode. This mode is used to set the volume when setVolumeCommandEnabled is false. If the volume change is not accurate, set a higher value. (default to 10). +* **setBurstModeEnabled**: enable/disable the use of the burst mode. If you cannot find a value first the burstMessageDelay, you may try to disable the burst mode. (default to true). +* **volumeMinDbZone1**: set the min volume value of zone 1 in dB (default to -80) +* **volumeMaxDbZone1**: set the max volume value of zone 1 in dB (default to 12) +* **volumeStepDbZone1**: set the number of dB the volume is increased/decreased for each step of zone 1 (default to 0.5) +* **volumeMinDbZone2**: set the min volume value of zone 2 in dB (default to -80) +* **volumeMaxDbZone2**: set the max volume value of zone 2 in dB (default to 0) +* **volumeStepDbZone2**: set the number of dB the volume is increased/decreased for each step of zone 2 (default to 1) +* **volumeMinDbZone3**: set the min volume value of zone 3 in dB (default to -80) +* **volumeMaxDbZone3**: set the max volume value of zone 3 in dB (default to 0) +* **volumeStepDbZone3**: set the number of dB the volume is increased/decreased for each step of zone 3 (default to 1) +* **volumeMinDbZone4**: set the min volume value of zone HD in dB (default to -80) +* **volumeMaxDbZone4**: set the max volume value of zone HD in dB (default to 0) +* **volumeStepDbZone4**: set the number of dB the volume is increased/decreased for each step of zone HD (default to 1) + + +Example for supported model: + +``` +pioneeravr:VSX-921:testVsx921IP [ ipAddress="192.168.1.25", tcpPort=8102 ] +pioneeravr:VSX-921:TestVsx921Serial [ useSerial=true, serialPort="COM9" ] ``` -pioneeravr:ipAvr:vsx921IP [ address="192.168.1.25", tcpPort="23" ] -pioneeravr:serialAvr:vsx921Serial [ serialPort="COM9" ] + +Example for unknown model: + +``` +pioneeravr:ConfigurablePioneerAVR:testConfigurableIPDefault [ ipAddress="192.168.1.25", tcpPort=8102 ] +pioneeravr:ConfigurablePioneerAVR:testConfigurableIPTweak [ ipAddress="192.168.1.25", tcpPort=8102 nbZones=2, setVolumeCommandEnabled=false, burstMessageDelay=50, setBurstModeEnabled=true ] ``` ## Channels -* power: power On/Off the AVR. Receive power events. -* volumeDimmer: Increase/Decrease the volume on the AVR or set the volume as %. Receive volume change events (in %). -* volumeDb: Set the volume of the AVR in dB (from -80.0 to 12 with 0.5 dB steps). Receive volume change events (in dB). -* mute: Mute/Unmute the AVR. Receive mute events. -* setInputSource: Set the input source of the AVR. See input source mapping for more details. Receive source input change events with the input source ID. -* displayInformation: Receive display events. Reflect the display on the AVR front panel. +For each zone (replace X by the zone number): +* **zoneX#power**: power On/Off the AVR. Receive power events. +* **zoneX#volumeDimmer**: Increase/Decrease the volume on the AVR or set the volume as %. Receive volume change events (in %). +* **zoneX#volumeDb**: Set the volume of the AVR in dB (from -80.0 to 12 with 0.5 dB steps). Receive volume change events (in dB). +* **zoneX#mute**: Mute/Unmute the AVR. Receive mute events. +* **zoneX#setInputSource**: Set the input source of the AVR. See input source mapping for more details. Receive source input change events with the input source ID. + +For an AVR: +* **displayInformation**: Receive display events. Reflect the display on the AVR front panel. -##Input Source Mapping +## Input Source Mapping Here after are the ID values of the input sources: @@ -77,25 +112,25 @@ Here after are the ID values of the input sources: ## Full example -*demo.Things: +* demo.Things: ``` -pioneeravr:ipAvr:vsx921 [ address="192.168.188.89" ] +pioneeravr:VSX-921:vsx921 [ ipAddress="192.168.1.25" ] ``` -*demo.items: +* demo.items: ``` /* Pioneer AVR Items */ -Switch vsx921PowerSwitch "Power" (All) { channel="pioneeravr:ipAvr:vsx921:power" } -Switch vsx921MuteSwitch "Mute" (All) { channel="pioneeravr:ipAvr:vsx921:mute" } -Dimmer vsx921VolumeDimmer "Volume [%.1f] %" (All) { channel="pioneeravr:ipAvr:vsx921:volumeDimmer" } -Number vsx921VolumeNumber "Volume [%.1f] dB" (All) { channel="pioneeravr:ipAvr:vsx921:volumeDb" } -String vsx921InputSourceSet "Input" (All) { channel="pioneeravr:ipAvr:vsx921:setInputSource" } -String vsx921InformationDisplay "Information [%s]" (All) { channel="pioneeravr:ipAvr:vsx921:displayInformation" } +Switch vsx921PowerSwitch "Power" (All) { channel="pioneeravr:VSX-921:vsx921:zone1#power" } +Switch vsx921MuteSwitch "Mute" (All) { channel="pioneeravr:VSX-921:vsx921:zone1#mute" } +Dimmer vsx921VolumeDimmer "Volume [%.1f] %" (All) { channel="pioneeravr:VSX-921:vsx921:zone1#volumeDimmer" } +Number vsx921VolumeNumber "Volume [%.1f] dB" (All) { channel="pioneeravr:VSX-921:vsx921:zone1#volumeDb" } +String vsx921InputSourceSet "Input" (All) { channel="pioneeravr:VSX-921:vsx921:zone1#setInputSource" } +String vsx921InformationDisplay "Information [%s]" (All) { channel="pioneeravr:VSX-921:vsx921:displayInformation" } ``` -*demo.sitemap: +* demo.sitemap: ``` sitemap demo label="Main Menu" @@ -104,7 +139,7 @@ sitemap demo label="Main Menu" Switch item=vsx921PowerSwitch Switch item=vsx921MuteSwitch mappings=[ON="Mute", OFF="Un-Mute"] Slider item=vsx921VolumeDimmer - Setpoint item=vsx921VolumeNumber minValue="-80" maxValue="12" step="0.5" + Setpoint item=vsx921VolumeNumber minValue=-80 maxValue=12 step=0.5 Switch item=vsx921InputSourceSet mappings=[04="DVD", 15="DVR/BDR", 25="BD"] Text item=vsx921InformationDisplay } diff --git a/addons/binding/org.openhab.binding.pioneeravr/about.html b/addons/binding/org.openhab.binding.pioneeravr/about.html index 1c1bb3d48eef4..7398e1109b461 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/about.html +++ b/addons/binding/org.openhab.binding.pioneeravr/about.html @@ -25,4 +25,4 @@

License

and such source code may be obtained at openhab.org.

- \ No newline at end of file + diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/PioneerAvrBindingConstants.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/PioneerAvrBindingConstants.java index 81506056e42ea..c0e5b2ae18bbf 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/PioneerAvrBindingConstants.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/PioneerAvrBindingConstants.java @@ -8,13 +8,8 @@ */ package org.openhab.binding.pioneeravr; -import java.util.Set; import java.util.regex.Pattern; -import org.eclipse.smarthome.core.thing.ThingTypeUID; - -import com.google.common.collect.ImmutableSet; - /** * The {@link PioneerAvrBinding} class defines common constants, which are used across the whole binding. * @@ -24,40 +19,44 @@ public class PioneerAvrBindingConstants { public static final String BINDING_ID = "pioneeravr"; - public static final Set SUPPORTED_DEVICE_MODELS = ImmutableSet.of("SC-57", "SC-LX85", "SC-55", "SC-1526", - "SC-LX75", "VSX-53", "VSX-1326", "VSX-LX55", "VSX-2021", "VSA-LX55", "VSX-52", "VSX-1126", "VSX-1121", - "VSX-51", "VSX-1021", "VSX-1026", "VSA-1021", "VSX-50", "VSX-926", "VSX-921", "VSA-921"); - - // List of all Thing Type UIDs - public static final ThingTypeUID IP_AVR_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipAvr"); - public static final ThingTypeUID IP_AVR_UNSUPPORTED_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipAvrUnsupported"); - public static final ThingTypeUID SERIAL_AVR_THING_TYPE = new ThingTypeUID(BINDING_ID, "serialAvr"); - - // List of thing parameters names - public static final String PROTOCOL_PARAMETER = "protocol"; - public static final String HOST_PARAMETER = "address"; - public static final String TCP_PORT_PARAMETER = "tcpPort"; - public static final String SERIAL_PORT_PARAMETER = "serialPort"; - - public static final String IP_PROTOCOL_NAME = "IP"; - public static final String SERIAL_PROTOCOL_NAME = "serial"; - - // List of all Channel names - public static final String POWER_CHANNEL = "power"; - public static final String VOLUME_DIMMER_CHANNEL = "volumeDimmer"; - public static final String VOLUME_DB_CHANNEL = "volumeDb"; - public static final String MUTE_CHANNEL = "mute"; - public static final String SET_INPUT_SOURCE_CHANNEL = "setInputSource"; - public static final String DISPLAY_INFORMATION_CHANNEL = "displayInformation#displayInformation"; - - public static final String GROUP_CHANNEL_PATTERN = "zone%s#%s"; - public static final Pattern GROUP_CHANNEL_ZONE_PATTERN = Pattern.compile("zone([0-3])#.*"); + public static final String PARAMETER_HOST = "ipAddress"; + public static final String PARAMETER_TCP_PORT = "tcpPort"; + public static final String PARAMETER_SERIAL_PORT = "serialPort"; + public static final String PARAMETER_USE_SERIAL = "useSerial"; + public static final String PARAMETER_USE_SET_VOLUME_COMMAND = "setVolumeCommandEnabled"; + public static final String PARAMETER_BURST_MESSAGE_DELAY = "burstMessageDelay"; + public static final String PARAMETER_USE_BURST_MODE = "setBurstModeEnabled"; + public static final String PARAMETER_NB_ZONES = "nbZones"; + public static final String PARAMETER_VOLUME_MIN_DB_ZONE1 = "volumeMinDbZone1"; + public static final String PARAMETER_VOLUME_MAX_DB_ZONE1 = "volumeMaxDbZone1"; + public static final String PARAMETER_VOLUME_STEP_DB_ZONE1 = "volumeStepDbZone1"; + public static final String PARAMETER_VOLUME_MIN_DB_ZONE2 = "volumeMinDbZone2"; + public static final String PARAMETER_VOLUME_MAX_DB_ZONE2 = "volumeMaxDbZone2"; + public static final String PARAMETER_VOLUME_STEP_DB_ZONE2 = "volumeStepDbZone2"; + public static final String PARAMETER_VOLUME_MIN_DB_ZONE3 = "volumeMinDbZone3"; + public static final String PARAMETER_VOLUME_MAX_DB_ZONE3 = "volumeMaxDbZone3"; + public static final String PARAMETER_VOLUME_STEP_DB_ZONE3 = "volumeStepDbZone3"; + public static final String PARAMETER_VOLUME_MIN_DB_ZONE4 = "volumeMinDbZone4"; + public static final String PARAMETER_VOLUME_MAX_DB_ZONE4 = "volumeMaxDbZone4"; + public static final String PARAMETER_VOLUME_STEP_DB_ZONE4 = "volumeStepDbZone4"; + + public static final String USE_SET_VOLUME_AUTO = "auto"; + + // List of all Channel ids + public static final String CHANNEL_ID_POWER = "power"; + public static final String CHANNEL_ID_VOLUME_DIMMER = "volumeDimmer"; + public static final String CHANNEL_ID_VOLUME_DB = "volumeDb"; + public static final String CHANNEL_ID_MUTE = "mute"; + public static final String CHANNEL_ID_SET_INPUT_SOURCE = "setInputSource"; + public static final String CHANNEL_ID_DISPLAY_INFORMATION = "displayInformation"; + + public static final String GROUP_CHANNEL_TYPE_ID_PATTERN = "%s-zone%s"; + + public static final String GROUP_CHANNEL_ID_PATTERN = "zone%s#%s"; + public static final Pattern GROUP_CHANNEL_ID_ZONE_PATTERN = Pattern.compile("zone([1-4])#.*"); // Used for Discovery service public static final String MANUFACTURER = "PIONEER"; public static final String UPNP_DEVICE_TYPE = "MediaRenderer"; - public static final Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(IP_AVR_THING_TYPE, - IP_AVR_UNSUPPORTED_THING_TYPE); - } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/AVRChannelTypeProvider.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/AVRChannelTypeProvider.java new file mode 100644 index 0000000000000..6237b19c51cc6 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/AVRChannelTypeProvider.java @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.channeltype; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.smarthome.core.thing.type.ChannelDefinition; +import org.eclipse.smarthome.core.thing.type.ChannelGroupDefinition; +import org.eclipse.smarthome.core.thing.type.ChannelGroupType; +import org.eclipse.smarthome.core.thing.type.ChannelGroupTypeUID; +import org.eclipse.smarthome.core.thing.type.ChannelType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeProvider; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.StateOption; +import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties.InputSource; + +/** + * Provides the {@link ChannelType} of all channels needed by all models of registered AVR. The registered AVRs have + * built there channels types through this {@link ChannelDefinitionFactory} implementation. + * + * @author Antoine Besnard + * + */ +public class AVRChannelTypeProvider implements ChannelTypeProvider, ChannelDefinitionFactory { + + private Map channelGroupTypes; + private Map channelTypes; + + public AVRChannelTypeProvider() { + this.channelGroupTypes = new HashMap<>(); + this.channelTypes = new HashMap<>(); + } + + @Override + public Collection getChannelTypes(Locale locale) { + return channelTypes.values(); + } + + @Override + public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) { + return channelTypes.get(channelTypeUID); + } + + @Override + public ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID, Locale locale) { + return channelGroupTypes.get(channelGroupTypeUID); + } + + @Override + public Collection getChannelGroupTypes(Locale locale) { + return channelGroupTypes.values(); + } + + @Override + public ChannelGroupDefinition getZoneChannelGroupDefinition(int zone, ModelProperties modelProperties) { + ChannelGroupDefinition result = new ChannelGroupDefinition("zone" + zone, + getZoneChannelGroupUID(zone, modelProperties)); + return result; + } + + @Override + public ChannelDefinition getPowerChannelDefinition(int zone, ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_POWER, + getPowerChannelType(zone, modelProperties).getUID(), null, "Power", null); + return result; + } + + @Override + public ChannelDefinition getVolumePercentChannelDefinition(int zone, ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DIMMER, + getVolumePercentChannelType(zone, modelProperties).getUID(), null, "Volume", null); + return result; + } + + @Override + public ChannelDefinition getVolumeDbChannelDefinition(int zone, ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DB, + getVolumeDbChannelType(zone, modelProperties).getUID(), null, "Volume", null); + return result; + } + + @Override + public ChannelDefinition getMuteChannelDefinition(int zone, ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_MUTE, + getMuteChannelType(zone, modelProperties).getUID(), null, "Mute", null); + return result; + } + + @Override + public ChannelDefinition getInformationChannelDefinition(ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_DISPLAY_INFORMATION, + getInformationChannelType(modelProperties).getUID(), null, "Display", null); + return result; + } + + @Override + public ChannelDefinition getInputSourceChannelDefinition(int zone, ModelProperties modelProperties) { + ChannelDefinition result = new ChannelDefinition(PioneerAvrBindingConstants.CHANNEL_ID_SET_INPUT_SOURCE, + getInputSourceChannelType(zone, modelProperties).getUID()); + return result; + } + + /** + * Build the {@link ChannelGroupTypeUID} for the given zone and {@link ModelProperties}. If no + * {@link ChannelGroupType} is currently registered for the built {@link ChannelGroupTypeUID}, it will be built and + * registered. + * + * @param zone + * @param modelProperties + * @return + */ + protected ChannelGroupTypeUID getZoneChannelGroupUID(int zone, ModelProperties modelProperties) { + // Define the UID of the channelGroupType + ChannelGroupTypeUID channelGroupTypeUID = new ChannelGroupTypeUID(PioneerAvrBindingConstants.BINDING_ID, + String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_TYPE_ID_PATTERN, modelProperties.getModelName(), + zone)); + + if (!channelGroupTypes.containsKey(channelGroupTypeUID)) { + // If not already done, create and register the channelGroupType for the UID. + + // List the channels of the channelGroupType + List channelDefinitions = new ArrayList<>(); + channelDefinitions.add(getPowerChannelDefinition(zone, modelProperties)); + channelDefinitions.add(getVolumePercentChannelDefinition(zone, modelProperties)); + channelDefinitions.add(getMuteChannelDefinition(zone, modelProperties)); + channelDefinitions.add(getInputSourceChannelDefinition(zone, modelProperties)); + + if (modelProperties.areDbChannelsEnabled()) { + channelDefinitions.add(getVolumeDbChannelDefinition(zone, modelProperties)); + } + + // Create the channelGroupType + ChannelGroupType channelGroupType = new ChannelGroupType(channelGroupTypeUID, false, "Zone " + zone, null, + null, channelDefinitions); + + // Then, register the channelGroupType + channelGroupTypes.put(channelGroupTypeUID, channelGroupType); + } + + return channelGroupTypeUID; + } + + protected ChannelType getPowerChannelType(int zone, ModelProperties modelProperties) { + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "powerChannel"); + + // If the channel type does not exist, create and register it. + ChannelType result = channelTypes.get(uid); + if (result == null) { + result = new ChannelType(uid, false, "Switch", "Power", "Power ON/OFF the AVR", null, null, null, null); + channelTypes.put(uid, result); + } + + return result; + } + + protected ChannelType getVolumePercentChannelType(int zone, ModelProperties modelProperties) { + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "volumePercentChannel"); + + // If the channel type does not exist, create and register it. + ChannelType result = channelTypes.get(uid); + if (result == null) { + StateDescription stateDescription = new StateDescription(BigDecimal.ZERO, BigDecimal.valueOf(100), + BigDecimal.ONE, "%d %%", false, null); + + result = new ChannelType(uid, false, "Dimmer", "Volume", + "Increase/Decrease the volume (%) and mute/un-mute", "SoundVolume", null, stateDescription, null); + channelTypes.put(uid, result); + } + + return result; + } + + protected ChannelType getVolumeDbChannelType(int zone, ModelProperties modelProperties) { + // Compute a hashCode to define channel uniquely by volume parameters + int hash = BigDecimal.valueOf(modelProperties.getVolumeMinDb(zone)).hashCode() * 21 + + BigDecimal.valueOf(modelProperties.getVolumeMaxDb(zone)).hashCode() * 31 + + BigDecimal.valueOf(modelProperties.getVolumeStepDb(zone)).hashCode() * 41; + + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "volumeDbChannel" + hash); + + ChannelType result = channelTypes.get(uid); + + // If the channel type does not exist, create and register it. + if (result == null) { + StateDescription stateDescription = new StateDescription( + BigDecimal.valueOf(modelProperties.getVolumeMinDb(zone)), + BigDecimal.valueOf(modelProperties.getVolumeMaxDb(zone)), + BigDecimal.valueOf(modelProperties.getVolumeStepDb(zone)), "%.1f dB", false, null); + + result = new ChannelType(uid, true, "Number", "Volume", "Set the volume level (dB)", "SoundVolume", null, + stateDescription, null); + channelTypes.put(uid, result); + } + + return result; + } + + protected ChannelType getMuteChannelType(int zone, ModelProperties modelProperties) { + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "muteChannel"); + + // If the channel type does not exist, create and register it. + ChannelType result = channelTypes.get(uid); + if (result == null) { + result = new ChannelType(uid, false, "Switch", "Mute", "Enable/Disable mute on the AVR", null, null, null, + null); + channelTypes.put(uid, result); + } + + return result; + } + + protected ChannelType getInformationChannelType(ModelProperties modelProperties) { + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "displayInformationChannel"); + + // If the channel type does not exist, create and register it. + ChannelType result = channelTypes.get(uid); + if (result == null) { + StateDescription stateDescription = new StateDescription(null, null, null, null, true, null); + result = new ChannelType(uid, false, "String", "Display", + "Display the information displayed on the AVR front screen", null, null, stateDescription, null); + channelTypes.put(uid, result); + } + + return result; + } + + protected ChannelType getInputSourceChannelType(int zone, ModelProperties modelProperties) { + int hash = 0; + if (modelProperties.getInputSources(zone) != null) { + for (InputSource inputSource : modelProperties.getInputSources(zone)) { + hash = hash * 11 + 31 * inputSource.getValue().hashCode() + 17 * inputSource.getName().hashCode(); + } + } + + ChannelTypeUID uid = new ChannelTypeUID(PioneerAvrBindingConstants.BINDING_ID, "setInputSourceChannel" + hash); + + // If the channel type does not exist, create and register it. + ChannelType result = channelTypes.get(uid); + if (result == null) { + List stateOptions = new ArrayList<>(); + if (modelProperties.getInputSources(zone) != null) { + for (InputSource inputSource : modelProperties.getInputSources(zone)) { + stateOptions.add(new StateOption(inputSource.getValue(), inputSource.getName())); + } + } + + StateDescription stateDescription = new StateDescription(null, null, null, null, false, stateOptions); + result = new ChannelType(uid, false, "String", "Input source", "Select the input source of the AVR", null, + null, stateDescription, null); + channelTypes.put(uid, result); + } + + return result; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/ChannelDefinitionFactory.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/ChannelDefinitionFactory.java new file mode 100644 index 0000000000000..dd441d561bb9d --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/channeltype/ChannelDefinitionFactory.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.channeltype; + +import org.eclipse.smarthome.core.thing.type.ChannelDefinition; +import org.eclipse.smarthome.core.thing.type.ChannelGroupDefinition; +import org.eclipse.smarthome.core.thing.type.ThingType; +import org.openhab.binding.pioneeravr.internal.models.AbstractModel; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; + +/** + * Factory that allows to create {@link ChannelDefinition} and {@link ChannelGroupDefinition} from zones and + * {@link ModelProperties}. + * + * Mainly used by {@link AbstractModel} to build the corresponding {@link ThingType} of an AVR model based on its + * {@link ModelProperties}. + * + * @author Antoine Besnard + * + */ +public interface ChannelDefinitionFactory { + + /** + * Return the {@link ChannelGroupDefinition} corresponding to the given zone for the given {@link ModelProperties}. + * Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelGroupDefinition getZoneChannelGroupDefinition(int zone, ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the PowerChannel corresponding to the given zone for the given + * {@link ModelProperties}. Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getPowerChannelDefinition(int zone, ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the VolumePercentChannel corresponding to the given zone for the given + * {@link ModelProperties}. Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getVolumePercentChannelDefinition(int zone, ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the VolumeDbChannel corresponding to the given zone for the given + * {@link ModelProperties}. Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getVolumeDbChannelDefinition(int zone, ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the MuteChannel corresponding to the given zone for the given + * {@link ModelProperties}. Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getMuteChannelDefinition(int zone, ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the InformationChannel for the given {@link ModelProperties}. Create it + * if it does not yet exists. + * + * This channel is unique on the AVR so it does not depends of a zone. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getInformationChannelDefinition(ModelProperties modelProperties); + + /** + * Return the {@link ChannelDefinition} of the InputSourceChannel corresponding to the given zone for the given + * {@link ModelProperties}. Create it if it does not yet exists. + * + * @param zone + * @param modelProperties + * @return + */ + ChannelDefinition getInputSourceChannelDefinition(int zone, ModelProperties modelProperties); + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/discovery/PioneerAvrDiscoveryParticipant.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/discovery/PioneerAvrDiscoveryParticipant.java index 252ffba1fc446..248b0411ac6b8 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/discovery/PioneerAvrDiscoveryParticipant.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/discovery/PioneerAvrDiscoveryParticipant.java @@ -8,6 +8,9 @@ */ package org.openhab.binding.pioneeravr.internal.discovery; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -21,15 +24,16 @@ import org.eclipse.smarthome.core.thing.ThingUID; import org.jupnp.model.meta.RemoteDevice; import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; +import org.openhab.binding.pioneeravr.internal.models.ConfigurableModel; +import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection; +import org.openhab.binding.pioneeravr.internal.thingtype.ThingTypeManager; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Collections2; - /** * An UpnpDiscoveryParticipant which allows to discover Pioneer AVRs. - * + * * @author Antoine Besnard * */ @@ -37,17 +41,17 @@ public class PioneerAvrDiscoveryParticipant implements UpnpDiscoveryParticipant private Logger logger = LoggerFactory.getLogger(PioneerAvrDiscoveryParticipant.class); + private ThingTypeManager modelManager; private boolean isAutoDiscoveryEnabled; private Set supportedThingTypes; public PioneerAvrDiscoveryParticipant() { this.isAutoDiscoveryEnabled = true; - this.supportedThingTypes = PioneerAvrBindingConstants.SUPPORTED_THING_TYPES_UIDS; } /** * Called at the service activation. - * + * * @param componentContext */ protected void activate(ComponentContext componentContext) { @@ -57,7 +61,7 @@ protected void activate(ComponentContext componentContext) { isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue); } } - supportedThingTypes = isAutoDiscoveryEnabled ? PioneerAvrBindingConstants.SUPPORTED_THING_TYPES_UIDS + supportedThingTypes = isAutoDiscoveryEnabled ? modelManager.getRegisteredThingTypesUIDs() : new HashSet(); } @@ -71,13 +75,14 @@ public DiscoveryResult createResult(RemoteDevice device) { DiscoveryResult result = null; ThingUID thingUid = getThingUID(device); if (thingUid != null) { - String label = StringUtils.isEmpty(device.getDetails().getFriendlyName()) ? device.getDisplayString() : device.getDetails().getFriendlyName(); Map properties = new HashMap<>(2, 1); - properties.put(PioneerAvrBindingConstants.HOST_PARAMETER, + properties.put(PioneerAvrBindingConstants.PARAMETER_HOST, device.getIdentity().getDescriptorURL().getHost()); - properties.put(PioneerAvrBindingConstants.PROTOCOL_PARAMETER, PioneerAvrBindingConstants.IP_PROTOCOL_NAME); + properties.put(PioneerAvrBindingConstants.PARAMETER_TCP_PORT, + getTcpPort(device.getIdentity().getDescriptorURL().getHost())); + properties.put(PioneerAvrBindingConstants.PARAMETER_USE_SERIAL, false); result = DiscoveryResultBuilder.create(thingUid).withLabel(label).withProperties(properties).build(); } @@ -89,7 +94,6 @@ public DiscoveryResult createResult(RemoteDevice device) { public ThingUID getThingUID(RemoteDevice device) { ThingUID result = null; if (isAutoDiscoveryEnabled) { - if (StringUtils.containsIgnoreCase(device.getDetails().getManufacturerDetails().getManufacturer(), PioneerAvrBindingConstants.MANUFACTURER)) { logger.debug("Manufacturer matched: search: {}, device value: {}.", @@ -101,11 +105,14 @@ public ThingUID getThingUID(RemoteDevice device) { PioneerAvrBindingConstants.UPNP_DEVICE_TYPE, device.getType().getType()); String deviceModel = device.getDetails().getModelDetails() != null - ? device.getDetails().getModelDetails().getModelName() : null; - ThingTypeUID thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE; - if (!isSupportedDeviceModel(deviceModel)) { - logger.debug("Device model {} not supported. Odd behaviors may happen.", deviceModel); - thingTypeUID = PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE; + ? device.getDetails().getModelDetails().getModelName() + : null; + ThingTypeUID thingTypeUID = getThingTypeUID(deviceModel); + logger.info("AVR model {} found.", deviceModel); + if (thingTypeUID.equals(ConfigurableModel.THING_TYPE_UID)) { + logger.warn( + "Device model {} not officialy supported. You may have to try different configurations for best results.", + deviceModel); } result = new ThingUID(thingTypeUID, device.getIdentity().getUdn().getIdentifierString()); @@ -117,19 +124,54 @@ public ThingUID getThingUID(RemoteDevice device) { } /** - * Return true only if the given device model is supported. - * + * Return the {@link ThingTypeUID} based on the deviceModel. + * * @param deviceModel * @return */ - private boolean isSupportedDeviceModel(final String deviceModel) { - return StringUtils.isNotBlank(deviceModel) - && !Collections2.filter(PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS, - new com.google.common.base.Predicate() { - public boolean apply(String input) { - return StringUtils.startsWithIgnoreCase(deviceModel, input); - } - }).isEmpty(); + private ThingTypeUID getThingTypeUID(String deviceModel) { + ThingTypeUID result = ConfigurableModel.THING_TYPE_UID; + for (ThingTypeUID thingTypeUID : supportedThingTypes) { + if (StringUtils.startsWithIgnoreCase(deviceModel, thingTypeUID.getId())) { + result = thingTypeUID; + break; + } + } + return result; + } + + /** + * Try to connect to the to possible telnet TCP ports of the AVR (depending of the model). + * + * @return + */ + private Integer getTcpPort(String host) { + Integer port = null; + try (Socket socket = new Socket()) { + try { + // Test the first possible telnet port. + port = IpAvrConnection.DEFAULT_TELNET_PORT_1; + socket.connect(new InetSocketAddress(host, port), 1000); + logger.info("Detected telnet port {} on AVR @{}", port, host); + } catch (IOException e) { + try { + // Test the second possible telnet port if the first has failed. + port = IpAvrConnection.DEFAULT_TELNET_PORT_2; + socket.connect(new InetSocketAddress(host, port), 1000); + logger.info("Detected telnet port {} on AVR @{}", port, host); + } catch (IOException e2) { + logger.warn("Unable to detect the telnet port on AVR @{}", host); + } + } + } catch (IOException e1) { + logger.error("Error closing telnet port auto detect socket."); + } + + return port; + } + + public void setModelManager(ThingTypeManager modelManager) { + this.modelManager = modelManager; } } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AbstractAvrHandler.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AbstractAvrHandler.java deleted file mode 100644 index af69094296237..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AbstractAvrHandler.java +++ /dev/null @@ -1,333 +0,0 @@ -/** - * Copyright (c) 2010-2017 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.pioneeravr.internal.handler; - -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; - -import org.eclipse.smarthome.core.library.types.DecimalType; -import org.eclipse.smarthome.core.library.types.OnOffType; -import org.eclipse.smarthome.core.library.types.PercentType; -import org.eclipse.smarthome.core.library.types.StringType; -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.binding.BaseThingHandler; -import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.UnDefType; -import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; -import org.openhab.binding.pioneeravr.internal.protocol.RequestResponseFactory; -import org.openhab.binding.pioneeravr.protocol.AvrConnection; -import org.openhab.binding.pioneeravr.protocol.AvrConnectionException; -import org.openhab.binding.pioneeravr.protocol.AvrResponse; -import org.openhab.binding.pioneeravr.protocol.CommandTypeNotSupportedException; -import org.openhab.binding.pioneeravr.protocol.event.AvrDisconnectionEvent; -import org.openhab.binding.pioneeravr.protocol.event.AvrDisconnectionListener; -import org.openhab.binding.pioneeravr.protocol.event.AvrStatusUpdateEvent; -import org.openhab.binding.pioneeravr.protocol.event.AvrUpdateListener; -import org.openhab.binding.pioneeravr.protocol.states.MuteStateValues; -import org.openhab.binding.pioneeravr.protocol.states.PowerStateValues; -import org.openhab.binding.pioneeravr.protocol.utils.DisplayInformationConverter; -import org.openhab.binding.pioneeravr.protocol.utils.VolumeConverter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link AbstractAvrHandler} is responsible for handling commands, which are sent to one of the channels through an - * AVR connection. - * - * @author Antoine Besnard - Initial contribution - */ -public abstract class AbstractAvrHandler extends BaseThingHandler - implements AvrUpdateListener, AvrDisconnectionListener { - - private Logger logger = LoggerFactory.getLogger(AbstractAvrHandler.class); - - private AvrConnection connection; - private ScheduledFuture statusCheckerFuture; - - public AbstractAvrHandler(Thing thing) { - super(thing); - this.connection = createConnection(); - - this.connection.addUpdateListener(this); - this.connection.addDisconnectionListener(this); - } - - /** - * Create a new connection to the AVR. - * - * @return - */ - protected abstract AvrConnection createConnection(); - - /** - * Initialize the state of the AVR. - */ - @Override - public void initialize() { - logger.debug("Initializing handler for Pioneer AVR @{}", connection.getConnectionName()); - updateStatus(ThingStatus.ONLINE); - - // Start the status checker - Runnable statusChecker = new Runnable() { - @Override - public void run() { - try { - logger.debug("Checking status of AVR @{}", connection.getConnectionName()); - checkStatus(); - } catch (LinkageError e) { - logger.warn( - "Failed to check the status for AVR @{}. If a Serial link is used to connect to the AVR, please check that the Bundle org.openhab.io.transport.serial is available. Cause: {}", - connection.getConnectionName(), e.getMessage()); - // Stop to check the status of this AVR. - if (statusCheckerFuture != null) { - statusCheckerFuture.cancel(false); - } - } - } - }; - statusCheckerFuture = scheduler.scheduleWithFixedDelay(statusChecker, 1, 10, TimeUnit.SECONDS); - } - - /** - * Close the connection and stop the status checker. - */ - @Override - public void dispose() { - super.dispose(); - if (statusCheckerFuture != null) { - statusCheckerFuture.cancel(true); - } - if (connection != null) { - connection.close(); - } - } - - /** - * Called when a Power ON state update is received from the AVR for the given zone. - */ - public void onPowerOn(int zone) { - // When the AVR is Powered ON, query the volume, the mute state and the source input of the zone - connection.sendVolumeQuery(zone); - connection.sendMuteQuery(zone); - connection.sendSourceInputQuery(zone); - - } - - /** - * Called when a Power OFF state update is received from the AVR. - */ - public void onPowerOff(int zone) { - // When the AVR is Powered OFF, update the status of channels to Undefined - updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, zone), UnDefType.UNDEF); - updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, zone), UnDefType.UNDEF); - updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, zone), UnDefType.UNDEF); - updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, zone), UnDefType.UNDEF); - - } - - /** - * Check the status of the AVR. Return true if the AVR is online, else return false. - * - * @return - */ - private void checkStatus() { - // If the power query request has not been sent, the connection to the - // AVR has failed. So update its status to OFFLINE. - if (!connection.sendPowerQuery(1)) { - updateStatus(ThingStatus.OFFLINE); - } else { - // If the power query has succeeded, the AVR status is ONLINE. - updateStatus(ThingStatus.ONLINE); - // Then send a power query for zone 2 and 3 - connection.sendPowerQuery(2); - connection.sendPowerQuery(3); - } - } - - /** - * Send a command to the AVR based on the openHAB command received. - */ - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - - try { - boolean commandSent = false; - boolean unknownCommand = false; - - if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) { - commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId())); - } else if (channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL) - || channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL)) { - commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId())); - } else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) { - commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId())); - } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) { - commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId())); - } else { - unknownCommand = true; - } - - // If the command is not unknown and has not been sent, the AVR is Offline - if (!commandSent && !unknownCommand) { - onDisconnection(); - } - } catch (CommandTypeNotSupportedException e) { - logger.warn("Unsupported command type received for channel {}.", channelUID.getId()); - } - } - - /** - * Called when a status update is received from the AVR. - */ - @Override - public void statusUpdateReceived(AvrStatusUpdateEvent event) { - try { - AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData()); - - switch (response.getResponseType()) { - case POWER_STATE: - managePowerStateUpdate(response); - break; - - case VOLUME_LEVEL: - manageVolumeLevelUpdate(response); - break; - - case MUTE_STATE: - manageMuteStateUpdate(response); - break; - - case INPUT_SOURCE_CHANNEL: - manageInputSourceChannelUpdate(response); - break; - - case DISPLAY_INFORMATION: - manageDisplayedInformationUpdate(response); - break; - - default: - logger.debug("Unkown response type from AVR @{}. Response discarded: {}", event.getData(), - event.getConnection()); - - } - } catch (AvrConnectionException e) { - logger.debug("Unkown response type from AVR @{}. Response discarded: {}", event.getData(), - event.getConnection()); - } - } - - /** - * Called when the AVR is disconnected - */ - @Override - public void onDisconnection(AvrDisconnectionEvent event) { - onDisconnection(); - } - - /** - * Process the AVR disconnection. - */ - private void onDisconnection() { - updateStatus(ThingStatus.OFFLINE); - } - - /** - * Notify an AVR power state update to openHAB - * - * @param response - */ - private void managePowerStateUpdate(AvrResponse response) { - OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON : OnOffType.OFF; - - // When a Power ON state update is received, call the onPowerOn method. - if (OnOffType.ON == state) { - onPowerOn(response.getZone()); - } else { - onPowerOff(response.getZone()); - } - - updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state); - } - - /** - * Notify an AVR volume level update to openHAB - * - * @param response - */ - private void manageVolumeLevelUpdate(AvrResponse response) { - - updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, response.getZone()), new DecimalType( - VolumeConverter.convertFromIpControlVolumeToDb(response.getParameterValue(), response.getZone()))); - updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, response.getZone()), - new PercentType((int) VolumeConverter.convertFromIpControlVolumeToPercent(response.getParameterValue(), - response.getZone()))); - } - - /** - * Notify an AVR mute state update to openHAB - * - * @param response - */ - private void manageMuteStateUpdate(AvrResponse response) { - updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, response.getZone()), - response.getParameterValue().equals(MuteStateValues.OFF_VALUE) ? OnOffType.OFF : OnOffType.ON); - } - - /** - * Notify an AVR input source channel update to openHAB - * - * @param response - */ - private void manageInputSourceChannelUpdate(AvrResponse response) { - updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()), - new StringType(response.getParameterValue())); - } - - /** - * Notify an AVR displayed information update to openHAB - * - * @param response - */ - private void manageDisplayedInformationUpdate(AvrResponse response) { - updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL, - new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue()))); - } - - /** - * Build the channelUID from the channel name and the zone number. - * - * @param channelName - * @param zone - * @return - */ - protected String getChannelUID(String channelName, int zone) { - return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName); - } - - /** - * Return the zone from the given channelUID. - * - * Return 0 if the zone cannot be extracted from the channelUID. - * - * @param channelUID - * @return - */ - protected int getZoneFromChannelUID(String channelUID) { - int zone = 0; - Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID); - if (matcher.find()) { - zone = Integer.valueOf(matcher.group(1)); - } - return zone; - } - -} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrConnectionHandler.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrConnectionHandler.java new file mode 100644 index 0000000000000..e5949c91242a8 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrConnectionHandler.java @@ -0,0 +1,346 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.handler; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.smarthome.core.library.types.DecimalType; +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.library.types.StringType; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.protocol.ParameterizedCommand.ParameterizedCommandType; +import org.openhab.binding.pioneeravr.internal.protocol.RequestResponseFactory; +import org.openhab.binding.pioneeravr.internal.protocol.SimpleCommand.SimpleCommandType; +import org.openhab.binding.pioneeravr.protocol.AvrCommand; +import org.openhab.binding.pioneeravr.protocol.AvrConnection; +import org.openhab.binding.pioneeravr.protocol.AvrResponse; +import org.openhab.binding.pioneeravr.protocol.CommandTypeNotSupportedException; +import org.openhab.binding.pioneeravr.protocol.utils.VolumeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles an AvrConnection. Translates an openHAB command to an AVR command and conversely. + * + * @author Antoine Besnard + */ +public class AvrConnectionHandler { + + private final Logger logger = LoggerFactory.getLogger(AvrConnectionHandler.class); + + private AvrConnection avrConnection; + private VolumeConverter volumeConverter; + private ReentrantLock setVolumeLock; + + private boolean isSetVolumeCommandEnabled; + + public AvrConnectionHandler(AvrConnection avrConnection, VolumeConverter volumeConverter, + ModelProperties modelProperties) { + this.avrConnection = avrConnection; + this.volumeConverter = volumeConverter; + this.setVolumeLock = new ReentrantLock(true); + this.isSetVolumeCommandEnabled = modelProperties.isSetVolumeCommandEnabled(); + + avrConnection.setBurstModeEnabled(modelProperties.isBurstModeEnabled()); + avrConnection.setBurstMessageDelay(modelProperties.getBurstMessageDelay()); + } + + /** + * Send a power state query to the AVR + * + * @param zone + * @return the response or null if the request has not been sent or the response has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendPowerQuery(int zone) throws TimeoutException { + return avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_QUERY, zone)); + } + + /** + * Send a volume level query to the AVR. + * + * @param zone + * @return the response or null if the request has not been sent or the response has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendVolumeQuery(int zone) throws TimeoutException { + return avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_QUERY, zone)); + } + + /** + * Send a mute state query to the AVR + * + * @param zone + * @return the response or null if the request has not been sent or the response has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendMuteQuery(int zone) throws TimeoutException { + return avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_QUERY, zone)); + } + + /** + * Send a source input state query to the AVR + * + * @param zone + * @return the response or null if the request has not been sent or the response has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendSourceInputQuery(int zone) throws TimeoutException { + return avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_QUERY, zone)); + } + + /** + * Send a display query command to the AVR + * + * @return + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendDisplayQuery() throws TimeoutException { + return avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.DISPLAY_QUERY, 1)); + } + + /** + * Send a power command to the AVR based on the openHAB command + * + * @param command + * @param zone + * @return a None response or null if the request has not been sent. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendPowerCommand(Command command, int zone) + throws CommandTypeNotSupportedException, TimeoutException { + AvrResponse response = null; + + if (command == OnOffType.ON) { + // Send the first Power ON command. + avrConnection.sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_ON, zone)); + + // According to the Pioneer Specs, the first request only wakeup the + // AVR CPU, the second one Power ON the AVR. Still according to the Pioneer Specs, the second + // request has to be delayed of 100 ms. + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + + // Then send the second request. + response = avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_ON, zone)); + } else if (command == OnOffType.OFF) { + avrConnection.sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_OFF, zone)); + } else if (command == RefreshType.REFRESH) { + response = sendPowerQuery(zone); + } else { + throw new CommandTypeNotSupportedException("Command type not supported."); + } + + return response; + } + + /** + * Send a volume command to the AVR based on the openHAB command + * + * @param command + * @param zone + * @return the response or null if the request has not been sent or has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendVolumeCommand(Command command, int zone) + throws CommandTypeNotSupportedException, TimeoutException { + AvrResponse response = null; + + // The OnOffType for volume is equal to the Mute command + if (command instanceof OnOffType) { + response = sendMuteCommand(command, zone); + } else if (command == IncreaseDecreaseType.DECREASE) { + AvrCommand commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_DOWN, zone); + response = avrConnection.sendCommand(commandToSend); + } else if (command == IncreaseDecreaseType.INCREASE) { + AvrCommand commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_UP, zone); + response = avrConnection.sendCommand(commandToSend); + } else if (command instanceof PercentType) { + String ipControlVolume = volumeConverter + .convertFromPercentToIpControlVolume(((PercentType) command).doubleValue(), zone); + response = sendSetVolume(ipControlVolume, zone); + logger.debug("Set volume to {} %", ((PercentType) command).doubleValue()); + } else if (command instanceof DecimalType) { + String ipControlVolume = volumeConverter + .convertFromDbToIpControlVolume(((DecimalType) command).doubleValue(), zone); + response = sendSetVolume(ipControlVolume, zone); + logger.debug("Set volume to {} dB", ((DecimalType) command).doubleValue()); + } else if (command == RefreshType.REFRESH) { + response = sendVolumeQuery(zone); + } else { + throw new CommandTypeNotSupportedException("Command type not supported."); + } + return response; + } + + /** + * Set the given volume on the AVR. Use directly the "Set Volume" command if it is supported, else send multiple + * "Volume Up" or "Volume Down" commands until the request volume is set. + * + * @param requestedIpControlVolume + * @param zone + * @return The last response of the process. + * @throws TimeoutException + */ + protected AvrResponse sendSetVolume(String requestedIpControlVolume, int zone) throws TimeoutException { + AvrResponse response = null; + + if (isSetVolumeCommandEnabled) { + // If the "Set Volume" command is supported, use it. + response = avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.VOLUME_SET, zone) + .setParameter(requestedIpControlVolume)); + } else { + // If the "Set Volume" command is not enabled, send as many "Volume Up" or "Volume Down" commands as + // needed. + response = sendSetVolumeBurstMode(requestedIpControlVolume, zone); + } + + return response; + } + + /** + * Set the given volume on the AVR using a burst of Volume UP/DOWN commands. + * + * @param requestedIpControlVolume + * @param zone + * @return + * @throws TimeoutException + */ + protected AvrResponse sendSetVolumeBurstMode(String requestedIpControlVolume, int zone) throws TimeoutException { + AvrResponse response = null; + + // Lock (with fairness) to serialize requests. + setVolumeLock.lock(); + + try { + // Get the current volume from the AVR. + response = sendVolumeQuery(zone); + + // If the AVR is online. + if (response != null) { + // Compute the number of requests to send (each request increments/decrements the volume of + // volumeStepDb) + Integer currentIpControlVolumeLevel = Integer.parseInt(response.getParameterValue()); + Integer requestedIpControlVolumeLevel = Integer.parseInt(requestedIpControlVolume); + Integer ipControlVolumeDelta = requestedIpControlVolumeLevel - currentIpControlVolumeLevel; + logger.debug( + "currentIpControlVolumeLevel: {}, requestedIpControlVolumeLevel: {}, ipControlVolumeDelta: {}", + currentIpControlVolumeLevel, requestedIpControlVolumeLevel, ipControlVolumeDelta); + + // Define the command to send (UP or DOWN) + SimpleCommandType commandType = ipControlVolumeDelta < 0 ? SimpleCommandType.VOLUME_DOWN + : SimpleCommandType.VOLUME_UP; + + // Set the number of commands to send. + Integer nbCommandsToSend = Math.abs(ipControlVolumeDelta); + logger.debug("nbCommandsToSend: {}", nbCommandsToSend); + + // Send the commands in burst. + avrConnection.sendBurstCommands(RequestResponseFactory.getIpControlCommand(commandType, zone), + nbCommandsToSend); + } + } finally { + // Unlock to release waiting threads. + setVolumeLock.unlock(); + } + + return response; + } + + /** + * Send a source input selection command to the AVR based on the openHAB command + * + * @param command + * @param zone + * @return the response or null if the request has not been sent or has timed out. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendInputSourceCommand(Command command, int zone) + throws CommandTypeNotSupportedException, TimeoutException { + AvrResponse response = null; + + if (command == IncreaseDecreaseType.INCREASE) { + response = avrConnection.sendCommand( + RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_CYCLIC, zone)); + } else if (command == IncreaseDecreaseType.DECREASE) { + response = avrConnection.sendCommand( + RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_REVERSE, zone)); + } else if (command instanceof StringType) { + String inputSourceValue = ((StringType) command).toString(); + response = avrConnection.sendCommand( + RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.INPUT_CHANNEL_SET, zone) + .setParameter(inputSourceValue)); + } else if (command == RefreshType.REFRESH) { + response = sendSourceInputQuery(zone); + } else { + throw new CommandTypeNotSupportedException("Command type not supported."); + } + + return response; + } + + /** + * Send a mute command to the AVR based on the openHAB command + * + * @param command + * @param zone + * @return a None response or null if the request has not been sent. + * @throws TimeoutException if no response is received. + */ + public AvrResponse sendMuteCommand(Command command, int zone) + throws CommandTypeNotSupportedException, TimeoutException { + AvrResponse response = null; + + if (command == OnOffType.ON) { + response = avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_ON, zone)); + } else if (command == OnOffType.OFF) { + response = avrConnection + .sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_OFF, zone)); + } else if (command == RefreshType.REFRESH) { + response = sendMuteQuery(zone); + } else { + throw new CommandTypeNotSupportedException("Command type not supported."); + } + + return response; + } + + /** + * Close the underlying connection. + */ + public void close() { + avrConnection.close(); + } + + /** + * Return the name of the underlying connection. + * + * @return + */ + public String getConnectionName() { + return avrConnection.getConnectionName(); + } +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandler.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandler.java new file mode 100644 index 0000000000000..57126211544b3 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandler.java @@ -0,0 +1,470 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.handler; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; + +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.StringType; +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.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.UnDefType; +import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; +import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType; +import org.openhab.binding.pioneeravr.internal.protocol.StreamAvrConnection; +import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection; +import org.openhab.binding.pioneeravr.internal.protocol.serial.SerialAvrConnection; +import org.openhab.binding.pioneeravr.protocol.AvrConnection; +import org.openhab.binding.pioneeravr.protocol.AvrConnectionException; +import org.openhab.binding.pioneeravr.protocol.AvrResponse; +import org.openhab.binding.pioneeravr.protocol.CommandTypeNotSupportedException; +import org.openhab.binding.pioneeravr.protocol.event.AvrConnectionListener; +import org.openhab.binding.pioneeravr.protocol.event.AvrNotificationEvent; +import org.openhab.binding.pioneeravr.protocol.event.AvrNotificationListener; +import org.openhab.binding.pioneeravr.protocol.states.MuteStateValues; +import org.openhab.binding.pioneeravr.protocol.states.PowerStateValues; +import org.openhab.binding.pioneeravr.protocol.utils.DisplayInformationConverter; +import org.openhab.binding.pioneeravr.protocol.utils.VolumeConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AvrHandler} is responsible for handling commands, which are sent to one of the channels through an + * {@link AvrConnectionHandler}. + * + * @author Antoine Besnard - Initial contribution + */ +public class AvrHandler extends BaseThingHandler implements AvrNotificationListener, AvrConnectionListener { + + private Logger logger = LoggerFactory.getLogger(AvrHandler.class); + + private ScheduledFuture connectionCheckerFuture; + + private ModelProperties modelProperties; + + private AvrConnectionHandler avrConnectionHandler; + + private VolumeConverter volumeConverter; + + @SuppressWarnings("null") + public AvrHandler(Thing thing, ModelProperties modelProperties) { + super(thing); + this.modelProperties = modelProperties; + } + + /** + * Initialize the AVR. Also called each times the configuration has changed. + */ + @Override + public void initialize() { + // Update the model properties with the thing configuration (configuration contains overridden properties only + // if the thing is a ConfigurableAvr). + modelProperties = ModelPropertiesImpl.cloneModelPropertiesAndOverrideWithConfiguration(modelProperties, + getThing().getConfiguration()); + + logger.debug("Initializing handler for Pioneer AVR {}", getThing().getUID()); + if (avrConnectionHandler != null) { + avrConnectionHandler.close(); + } + + this.volumeConverter = new VolumeConverter(modelProperties); + + avrConnectionHandler = new AvrConnectionHandler(createConnection(), volumeConverter, modelProperties); + logger.debug("Handler for Pioneer AVR @{} initialized", avrConnectionHandler.getConnectionName()); + + checkStatus(); + } + + @Override + public void handleRemoval() { + avrConnectionHandler.close(); + + super.handleRemoval(); + } + + /** + * Create a new connection to the AVR. + * + * @return + */ + private AvrConnection createConnection() { + Boolean useSerial = (Boolean) this.getConfig().get(PioneerAvrBindingConstants.PARAMETER_USE_SERIAL); + StreamAvrConnection connection = null; + + // If the useSerial parameter is set to true + if (useSerial != null && useSerial) { + // Use a serial connection + String serialPort = (String) this.getConfig().get(PioneerAvrBindingConstants.PARAMETER_SERIAL_PORT); + + // Create a Serial connection + connection = new SerialAvrConnection(scheduler, serialPort); + } else { + // Else use an IP connection. + String host = (String) this.getConfig().get(PioneerAvrBindingConstants.PARAMETER_HOST); + Number tcpPort = (Number) this.getConfig().get(PioneerAvrBindingConstants.PARAMETER_TCP_PORT); + + // Create an IP Connection + connection = new IpAvrConnection(scheduler, host, tcpPort != null ? tcpPort.intValue() : null); + } + connection.addNotificationListener(this); + connection.addConnectionListener(this); + + return connection; + } + + /** + * Close the connection and stop the status checker. + */ + @Override + public void dispose() { + super.dispose(); + stopConnectionChecker(); + if (avrConnectionHandler != null) { + avrConnectionHandler.close(); + } + } + + /** + * Start the status checker. Should be started only when the AVR is offline. + */ + private synchronized void startConnectionChecker() { + if (connectionCheckerFuture == null) { + // Start the connection checker + Runnable statusChecker = new Runnable() { + @Override + public void run() { + try { + checkStatus(); + } catch (LinkageError e) { + logger.warn( + "Failed to check the connection for AVR @{}. If a Serial link is used to connect to the AVR, please check that the Bundle org.openhab.io.transport.serial is available. Cause: {}", + avrConnectionHandler.getConnectionName(), e.getMessage()); + // Stop to check the connection with this AVR. + stopConnectionChecker(); + } + } + }; + connectionCheckerFuture = scheduler.scheduleWithFixedDelay(statusChecker, 1, 10, TimeUnit.SECONDS); + } + } + + /** + * Stop the status checker. Should be called when the AVR is online. + */ + private synchronized void stopConnectionChecker() { + if (connectionCheckerFuture != null) { + if (connectionCheckerFuture != null) { + connectionCheckerFuture.cancel(false); + connectionCheckerFuture = null; + } + } + } + + /** + * Called when a Power ON state update is received from the AVR for the given zone. + */ + public void onPowerOn(int zone) { + try { + // When the AVR is Powered ON, query the volume, the mute state and the source input of the zone + manageVolumeLevelUpdate(avrConnectionHandler.sendVolumeQuery(zone)); + manageMuteStateUpdate(avrConnectionHandler.sendMuteQuery(zone)); + manageInputSourceChannelUpdate(avrConnectionHandler.sendSourceInputQuery(zone)); + } catch (TimeoutException e) { + logger.error("Timeout when updating state of zone {} of AVR @{} after powerOn. Cause: {}", zone, + avrConnectionHandler.getConnectionName(), e.getMessage()); + } + } + + /** + * Called when a Power OFF state update is received from the AVR. + */ + public void onPowerOff(int zone) { + // When the AVR is Powered OFF, update the status of channels to Undefined + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_MUTE, zone), UnDefType.UNDEF); + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DB, zone), UnDefType.UNDEF); + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DIMMER, zone), UnDefType.UNDEF); + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_SET_INPUT_SOURCE, zone), UnDefType.UNDEF); + } + + /** + * Check the status of the AVR. Return true if the AVR is online, else return false. + * + * @return + */ + private void checkStatus() { + try { + logger.debug("Checking status of AVR @{}", avrConnectionHandler.getConnectionName()); + // If the power query request has not been sent, the connection to the + // AVR has failed. So update its status to OFFLINE and start the connection checker. + AvrResponse response = avrConnectionHandler.sendPowerQuery(1); + if (response == null) { + updateStatus(ThingStatus.OFFLINE); + startConnectionChecker(); + } + // If a response is received, the AVR is ONLINE, but onConnection has been called by the connection, + // so do nothing here. + } catch (TimeoutException e) { + // Timeout on the response. Since the request has been sent, the connection is open + // => AVR is ONLINE, but onConnection has been called by the connection, + // so do nothing here. Just log as debug + logger.debug("Timeout during checkStatus.", e); + } + } + + /** + * Update all supported zones. + */ + protected void updateZones() { + for (int zone = 1; zone <= modelProperties.getNbZones(); zone++) { + // Check all supported zones + if (isZoneSupported(zone)) { + try { + managePowerStateUpdate(avrConnectionHandler.sendPowerQuery(zone)); + } catch (TimeoutException e) { + logger.error("Timeout when updating the zone {}. Cause: {}", zone, e.getMessage()); + } + } + } + } + + /** + * Check if the given zone is supported. + * + * @param zone + * @return true if the zone is strictly positive and supported by the AVR, else false. + */ + protected boolean isZoneSupported(int zone) { + return zone > 0 && zone <= modelProperties.getNbZones(); + } + + /** + * Handle an openHAB command and translate it to an AVR command. + */ + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + try { + // The display information channel is not bound to a zone. So process it directly. + if (channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_DISPLAY_INFORMATION) + && command == RefreshType.REFRESH) { + manageDisplayedInformationUpdate(avrConnectionHandler.sendDisplayQuery()); + } else { + // Extract the zone from the Channel UID + int zone = getZoneFromChannelUID(channelUID.getId()); + + // If the requested zone is not supported by the AVR, + // then do not send the request. + if (isZoneSupported(zone)) { + if (channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_POWER)) { + managePowerStateUpdate(avrConnectionHandler.sendPowerCommand(command, zone)); + } else if (channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DIMMER) + || channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DB)) { + manageVolumeLevelUpdate(avrConnectionHandler.sendVolumeCommand(command, zone)); + } else if (channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_SET_INPUT_SOURCE)) { + manageInputSourceChannelUpdate(avrConnectionHandler.sendInputSourceCommand(command, zone)); + } else if (channelUID.getId().contains(PioneerAvrBindingConstants.CHANNEL_ID_MUTE)) { + manageMuteStateUpdate(avrConnectionHandler.sendMuteCommand(command, zone)); + } + } else { + logger.warn("Command for zone {} not send since zone {} is not supported by the AVR@{}.", zone, + zone, avrConnectionHandler.getConnectionName()); + } + } + } catch (CommandTypeNotSupportedException e) { + logger.info("Unsupported command type {} received for channel {}.", command.toFullString(), + channelUID.getId()); + } catch (TimeoutException e) { + logger.error("Timeout when processing command {} of channel {} of thing {}. Cause: {}", + command.toFullString(), channelUID, getThing().getUID(), e.getMessage()); + } + } + + /** + * Called when a status update is received from the AVR. + * + * The responses to explicit requests do not trigger this update. + */ + @Override + public void statusUpdateReceived(AvrNotificationEvent event) { + try { + AvrResponse notification = event.getNotification(); + + switch (notification.getResponseType()) { + case POWER_STATE: + managePowerStateUpdate(notification); + break; + case VOLUME_LEVEL: + manageVolumeLevelUpdate(notification); + break; + case MUTE_STATE: + manageMuteStateUpdate(notification); + break; + case INPUT_SOURCE_CHANNEL: + manageInputSourceChannelUpdate(notification); + break; + case DISPLAY_INFORMATION: + manageDisplayedInformationUpdate(notification); + break; + default: + logger.debug("Unkown notification type from AVR @{}. Notification discarded: {}", + event.getNotification(), event.getConnection()); + } + } catch (AvrConnectionException e) { + logger.debug("Unkown notification type from AVR @{}. Notification discarded: {}", event.getNotification(), + event.getConnection()); + } + } + + /** + * Called when the AVR is disconnected + */ + @Override + public void onDisconnection(AvrConnection connection, Throwable cause) { + logger.warn("The AVR @{} is disconnected. Cause: {}", connection.getConnectionName(), + cause != null ? cause.getMessage() : "unknown"); + updateStatus(ThingStatus.OFFLINE); + startConnectionChecker(); + } + + /** + * Called when the AVR is connected + */ + @Override + public void onConnection(AvrConnection connection) { + logger.info("The AVR @{} is connected.", connection.getConnectionName()); + updateStatus(ThingStatus.ONLINE); + + // Stop the connection checker + stopConnectionChecker(); + // Update the zones support and status + updateZones(); + + try { + // Update the display + manageDisplayedInformationUpdate(avrConnectionHandler.sendDisplayQuery()); + } catch (TimeoutException e) { + // Log as debug since the response is not guaranteed (as said in the protocol specifications) + logger.debug( + "Failed to update the AVR @{} display after connection. That error can be ignored as said in the protocol specification.", + connection.getConnectionName()); + } + } + + /** + * Notify an AVR power state update to OpenHAB + * + * @param response + */ + private void managePowerStateUpdate(AvrResponse response) { + if (response != null) { + OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON + : OnOffType.OFF; + + // When a Power ON state update is received, call the onPowerOn method. + if (OnOffType.ON == state) { + onPowerOn(response.getZone()); + } else { + onPowerOff(response.getZone()); + } + + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_POWER, response.getZone()), state); + } + } + + /** + * Notify an AVR volume level update to OpenHAB + * + * @param response + */ + private void manageVolumeLevelUpdate(AvrResponse response) { + if (response != null && response.getResponseType() != ResponseType.NONE) { + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DB, response.getZone()), + new DecimalType(volumeConverter.convertFromIpControlVolumeToDb(response.getParameterValue(), + response.getZone()))); + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_VOLUME_DIMMER, response.getZone()), + new PercentType((int) volumeConverter + .convertFromIpControlVolumeToPercent(response.getParameterValue(), response.getZone()))); + } + } + + /** + * Notify an AVR mute state update to OpenHAB + * + * @param response + */ + private void manageMuteStateUpdate(AvrResponse response) { + if (response != null) { + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_MUTE, response.getZone()), + response.getParameterValue().equals(MuteStateValues.OFF_VALUE) ? OnOffType.OFF : OnOffType.ON); + } + } + + /** + * Notify an AVR input source channel update to OpenHAB + * + * @param response + */ + private void manageInputSourceChannelUpdate(AvrResponse response) { + if (response != null) { + updateState(getChannelUID(PioneerAvrBindingConstants.CHANNEL_ID_SET_INPUT_SOURCE, response.getZone()), + new StringType(response.getParameterValue())); + } + } + + /** + * Notify an AVR displayed information update to OpenHAB + * + * @param response + */ + private void manageDisplayedInformationUpdate(AvrResponse response) { + if (response != null) { + updateState(PioneerAvrBindingConstants.CHANNEL_ID_DISPLAY_INFORMATION, new StringType( + DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue()))); + } + } + + /** + * Build the channelUID from the channel name and the zone number. + * + * @param channelName + * @param zone + * @return + */ + protected String getChannelUID(String channelName, int zone) { + return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_ID_PATTERN, zone, channelName); + } + + /** + * Return the zone from the given channelUID. + * + * Return 0 if the zone cannot be extracted from the channelUID. + * + * @param channelUID + * @return + */ + protected int getZoneFromChannelUID(String channelUID) { + int zone = 0; + Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ID_ZONE_PATTERN.matcher(channelUID); + if (matcher.find()) { + zone = Integer.valueOf(matcher.group(1)); + } + return zone; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandlerFactory.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandlerFactory.java index 5a5002c2ada9d..37ef0dee99003 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandlerFactory.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/AvrHandlerFactory.java @@ -8,17 +8,12 @@ */ package org.openhab.binding.pioneeravr.internal.handler; -import java.util.Map; -import java.util.Set; - import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; import org.eclipse.smarthome.core.thing.binding.ThingHandler; -import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; -import org.osgi.service.component.ComponentContext; - -import com.google.common.collect.Sets; +import org.openhab.binding.pioneeravr.internal.models.AbstractModel; +import org.openhab.binding.pioneeravr.internal.thingtype.ThingTypeManager; /** * The {@link AvrHandlerFactory} is responsible for creating things and thing handlers. @@ -27,32 +22,26 @@ */ public class AvrHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Sets.newHashSet( - PioneerAvrBindingConstants.IP_AVR_THING_TYPE, PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE, - PioneerAvrBindingConstants.SERIAL_AVR_THING_TYPE); - - protected void activate(ComponentContext componentContext, Map configProps) { - super.activate(componentContext); - - } + private ThingTypeManager thingTypeManager; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + return thingTypeManager.getRegisteredThingTypesUIDs().contains(thingTypeUID); } @Override protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + AbstractModel model = thingTypeManager.getModelFromThingType(thingTypeUID); - if (thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE) - || thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE)) { - return new IpAvrHandler(thing); - } else if (thingTypeUID.equals(PioneerAvrBindingConstants.SERIAL_AVR_THING_TYPE)) { - return new SerialAvrHandler(thing); + if (model != null) { + return new AvrHandler(thing, model.getModelProperties()); } return null; } + + public void setModelManager(ThingTypeManager modelManager) { + this.thingTypeManager = modelManager; + } } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/IpAvrHandler.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/IpAvrHandler.java deleted file mode 100644 index f351154ea9c0a..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/IpAvrHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2017 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.pioneeravr.internal.handler; - -import org.eclipse.smarthome.core.thing.Thing; -import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; -import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection; -import org.openhab.binding.pioneeravr.protocol.AvrConnection; - -/** - * An handler of an AVR connected through an IP connection. - * - * @author Antoine Besnard - * - */ -public class IpAvrHandler extends AbstractAvrHandler { - - public IpAvrHandler(Thing thing) { - super(thing); - } - - @Override - protected AvrConnection createConnection() { - String host = (String) this.getConfig().get(PioneerAvrBindingConstants.HOST_PARAMETER); - Integer tcpPort = ((Number) this.getConfig().get(PioneerAvrBindingConstants.TCP_PORT_PARAMETER)).intValue(); - - return new IpAvrConnection(host, tcpPort); - } - -} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/SerialAvrHandler.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/SerialAvrHandler.java deleted file mode 100644 index 9ff0315fd0886..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/handler/SerialAvrHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2010-2017 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.pioneeravr.internal.handler; - -import org.eclipse.smarthome.core.thing.Thing; -import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; -import org.openhab.binding.pioneeravr.internal.protocol.serial.SerialAvrConnection; -import org.openhab.binding.pioneeravr.protocol.AvrConnection; - -/** - * An handler of an AVR connected through a serial port. - * - * @author Antoine Besnard - * - */ -public class SerialAvrHandler extends AbstractAvrHandler { - - public SerialAvrHandler(Thing thing) { - super(thing); - } - - @Override - protected AvrConnection createConnection() { - String serialPort = (String) this.getConfig().get(PioneerAvrBindingConstants.SERIAL_PORT_PARAMETER); - - return new SerialAvrConnection(serialPort); - } - -} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/AbstractModel.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/AbstractModel.java new file mode 100644 index 0000000000000..50025a3cc0476 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/AbstractModel.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.type.ChannelDefinition; +import org.eclipse.smarthome.core.thing.type.ChannelGroupDefinition; +import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; + +/** + * Base class for an AVR Model. + * + * @author Antoine Besnard + * + */ +public abstract class AbstractModel { + + public static final URI GENERIC_AVR_CONFIG_DESCRIPTION_URI = URI.create("thing-type:pioneeravr:genericConfig"); + public static final URI SUPPORTED_AVR_CONFIG_DESCRIPTION_URI = URI.create("thing-type:pioneeravr:supportedConfig"); + + private ModelProperties modelProperties; + + private ChannelDefinitionFactory channelDefinitionFactory; + + private ThingTypeUID thingTypeUID; + + public AbstractModel(ChannelDefinitionFactory channelDefinitionFactory) { + this.channelDefinitionFactory = channelDefinitionFactory; + this.modelProperties = initModelProperties(); + this.thingTypeUID = new ThingTypeUID(PioneerAvrBindingConstants.BINDING_ID, getThingTypeId()); + } + + public ThingTypeUID getThingTypeUID() { + return this.thingTypeUID; + } + + public String getThingTypeId() { + return modelProperties.getModelName(); + } + + public String getThingTypeLabel() { + return modelProperties.getModelName(); + } + + public int getNbZones() { + return modelProperties.getNbZones(); + } + + public List getChannelDefinitions() { + List channelDefinitions = new ArrayList<>(); + channelDefinitions.add(channelDefinitionFactory.getInformationChannelDefinition(modelProperties)); + return channelDefinitions; + } + + public List getChannelGroupDefinitions() { + List channelGroupDefinitions = new ArrayList<>(); + for (int zone = 1; zone <= getNbZones(); zone++) { + channelGroupDefinitions.add(channelDefinitionFactory.getZoneChannelGroupDefinition(zone, modelProperties)); + } + return channelGroupDefinitions; + } + + public String getThingTypeDescription() { + return modelProperties.getModelDescription(); + } + + public URI getThingTypeConfigDescriptionURI() { + return SUPPORTED_AVR_CONFIG_DESCRIPTION_URI; + } + + public ModelProperties getModelProperties() { + return modelProperties; + } + + /** + * Called by the constructor to initialize the model properties. + * + * @return + */ + protected abstract ModelProperties initModelProperties(); + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/ConfigurableModel.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/ConfigurableModel.java new file mode 100644 index 0000000000000..a0454db86583c --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/ConfigurableModel.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import java.net.URI; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; + +/** + * The model that represent a generic AVR (i.e. a model that is not already supported). + * + * The properties of this model can be modified on the fly to test a configuration. Once a configuration is found for a + * specific model, a well defined model should be created for it. + * + * @author Antoine Besnard + * + */ +public class ConfigurableModel extends AbstractModel { + + public ConfigurableModel(ChannelDefinitionFactory channelManager) { + super(channelManager); + } + + public static final String MODEL_NAME = "ConfigurablePioneerAVR"; + + public static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID(PioneerAvrBindingConstants.BINDING_ID, + MODEL_NAME); + + @Override + protected ModelProperties initModelProperties() { + // Keep default parameters. + // Define 4 zones. + ModelPropertiesImpl modelPropertiesImpl = new ModelPropertiesImpl(MODEL_NAME, 4); + + modelPropertiesImpl + .setModelDescription("An unknown AVR model. Parameter of this model can be modified on the fly."); + + // Disable the dB channel since the dB values may be customized by the users + // (The dB channel state may not reflect the configured dB values) + modelPropertiesImpl.setDbChannelsEnabled(false); + + // A delay of 10 ms seems to work well. + modelPropertiesImpl.setBurstMessageDelay(10); + + return modelPropertiesImpl; + } + + @Override + public ThingTypeUID getThingTypeUID() { + return THING_TYPE_UID; + } + + @Override + public URI getThingTypeConfigDescriptionURI() { + return AbstractModel.GENERIC_AVR_CONFIG_DESCRIPTION_URI; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1021.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1021.java new file mode 100644 index 0000000000000..5bc8f98081ba3 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1021.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; + +/** + * + * @author Antoine Besnard + * + */ +public class VSX1021 extends AbstractModel { + + public VSX1021(ChannelDefinitionFactory channelManager) { + super(channelManager); + } + + public static final String MODEL_NAME = "VSX-1021"; + + @Override + protected ModelProperties initModelProperties() { + ModelPropertiesImpl modelProperties = new ModelPropertiesImpl(MODEL_NAME, 2); + + // For these AVR model, keep all default values. + + return modelProperties; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1120.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1120.java new file mode 100644 index 0000000000000..317fd567b3588 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1120.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; + +/** + * + * @author Antoine Besnard + * + */ +public class VSX1120 extends AbstractModel { + + public VSX1120(ChannelDefinitionFactory channelManager) { + super(channelManager); + } + + public static final String MODEL_NAME = "VSX-1120"; + + @Override + protected ModelProperties initModelProperties() { + ModelPropertiesImpl modelProperties = new ModelPropertiesImpl(MODEL_NAME, 2); + + // For these AVR model, keep all default values. + + return modelProperties; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1122.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1122.java new file mode 100644 index 0000000000000..7a8ac55f69c68 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX1122.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; + +/** + * + * @author Antoine Besnard + * + */ +public class VSX1122 extends AbstractModel { + + public VSX1122(ChannelDefinitionFactory channelManager) { + super(channelManager); + } + + public static final String MODEL_NAME = "VSX-1122"; + + @Override + protected ModelProperties initModelProperties() { + ModelPropertiesImpl modelProperties = new ModelPropertiesImpl(MODEL_NAME, 2); + + // For these AVR model, keep all default values. + + return modelProperties; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX921.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX921.java new file mode 100644 index 0000000000000..8e0e4dd6a7cf9 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/VSX921.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models; + +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelPropertiesImpl; + +/** + * + * @author Antoine Besnard + * + */ +public class VSX921 extends AbstractModel { + + public VSX921(ChannelDefinitionFactory channelDefinitionFactory) { + super(channelDefinitionFactory); + } + + public static final String MODEL_NAME = "VSX-921"; + + @Override + protected ModelProperties initModelProperties() { + ModelPropertiesImpl modelProperties = new ModelPropertiesImpl(MODEL_NAME, 2); + + // For these AVR model, keep all default values. + + return modelProperties; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelProperties.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelProperties.java new file mode 100644 index 0000000000000..8712451dbb8d1 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelProperties.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models.properties; + +import java.util.Collection; + +/** + * Defines the properties for a specific AVR model. + * + * @author Antoine Besnard + * + */ +public interface ModelProperties { + + // Volume Format / MAX_IP / Min_DB for Zones + // Zone1 Command ***VL 000 to 185 (-80.0dB - +12.0dB - 1step = 0.5dB) + // Zone2 Command **ZV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) + // Zone3 Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) + // HDZone Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) + public static final double[] DEFAULT_MIN_DB = { -80, -80, -80, -80 }; + public static final double[] DEFAULT_MAX_DB = { 12, 0, 0, 0 }; + public static final double[] DEFAULT_STEP_DB = { 0.5, 1, 1, 1 }; + + // In burst mode, wait for 10 ms between two messages. + public static final int DEFAULT_BURST_MESSAGE_DELAY = 10; + + String getModelName(); + + String getModelDescription(); + + int getNbZones(); + + double getVolumeMinDb(int zone); + + double getVolumeMaxDb(int zone); + + double getVolumeStepDb(int zone); + + Collection getInputSources(int zone); + + /** + * Enable/Disable the use of the SetVolume command. + * + * @return + */ + boolean isSetVolumeCommandEnabled(); + + /** + * Allow or not to control the AVR through the dB channels. + * + * @return + */ + boolean areDbChannelsEnabled(); + + /** + * Return the delay (in milliseconds) between two messages send to the AVR in burst mode. + * (mainly used to set the volume on the AVR when the SetVolume command is not enabled). + * + * If the delay is too short, some messages may be missed by the AVR and the volume level will not be accurate. If + * too high, the volume will be longer to + * adjust to the requested value. + * + * @return + */ + int getBurstMessageDelay(); + + /** + * Enable/Disable the burst mode. If disabled, the burst commands are sent in "slow" mode. Should only be disabled + * in last resort. + * + * @return + */ + boolean isBurstModeEnabled(); + + enum InputSource { + DVD("DVD", "04"), + BD("BD", "25"), + TV_SAT("TV/SAT", "05"), + DVR_BDR("DVR/BDR", "15"), + VIDEO_1("VIDEO 1", "10"), + VIDEO_2("VIDEO 2", "14"), + HDMI_1("HDMI 1", "19"), + HDMI_2("HDMI 2", "20"), + HDMI_3("HDMI 3", "21"), + HDMI_4("HDMI 4", "22"), + HDMI_5("HDMI 5", "23"), + HOME_MEDIA_GALLERY("HOME MEDIA GALLERY", "26"), + IPOD_USB("iPod/USB", "17"), + XM_RADIO("XM RADIO", "18"), + CD("CD", "01"), + CD_R_TAPE("CD-R/TAPE", "03"), + TUNER("TUNER", "02"), + PHONO("PHONO", "00"), + MULTI_CH_IN("MULTI CH IN", "12"), + ADAPTER_PORT("ADAPTER PORT", "33"), + SIRIUS("SIRIUS", "27"); + + private String name; + private String value; + + private InputSource(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelPropertiesImpl.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelPropertiesImpl.java new file mode 100644 index 0000000000000..f91364c84139c --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/models/properties/ModelPropertiesImpl.java @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2014-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.models.properties; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.smarthome.config.core.Configuration; +import org.openhab.binding.pioneeravr.PioneerAvrBindingConstants; + +import com.google.common.collect.Lists; + +/** + * + * @author Antoine Besnard + * + */ +public class ModelPropertiesImpl implements ModelProperties { + + private String modelName; + private String modelDescription; + private boolean isSetVolumeCommandEnabled; + private int nbZones; + private List zonesVolumeMinDb; + private List zonesVolumeMaxDb; + private List zonesVolumeStepDb; + private List> inputSources; + private boolean areDbChannelsEnabled; + private int burstMessageDelay; + private boolean isBurstModeEnabled; + + public ModelPropertiesImpl(String modelName, int nbZones) { + this.modelName = modelName; + this.modelDescription = "A Pioneer AVR " + modelName; + this.nbZones = nbZones; + this.zonesVolumeMinDb = new ArrayList<>(nbZones); + this.zonesVolumeMaxDb = new ArrayList<>(nbZones); + this.zonesVolumeStepDb = new ArrayList<>(nbZones); + this.inputSources = new ArrayList<>(nbZones); + this.isSetVolumeCommandEnabled = true; + this.areDbChannelsEnabled = true; + this.burstMessageDelay = DEFAULT_BURST_MESSAGE_DELAY; + this.isBurstModeEnabled = true; + + // Set default values + for (int zone = 1; zone <= nbZones; zone++) { + this.zonesVolumeMaxDb.add(DEFAULT_MAX_DB[zone - 1]); + this.zonesVolumeMinDb.add(DEFAULT_MIN_DB[zone - 1]); + this.zonesVolumeStepDb.add(DEFAULT_STEP_DB[zone - 1]); + this.inputSources.add(Lists.newArrayList(InputSource.values())); + } + } + + /** + * Clone the given ModelProperties. + * + * If the source {@link ModelProperties} has more zones than nbZones, the extra zones from the source model are + * discarded. + * + * If the source {@link ModelProperties} has less zones than nbZones, the extra zones in the target model are + * initialized with default values. + * + * @param baseModelProperties + */ + public ModelPropertiesImpl(ModelProperties baseModelProperties, int nbZones) { + this.modelName = baseModelProperties.getModelName(); + this.modelDescription = baseModelProperties.getModelDescription(); + this.nbZones = nbZones; + this.zonesVolumeMinDb = new ArrayList<>(nbZones); + this.zonesVolumeMaxDb = new ArrayList<>(nbZones); + this.zonesVolumeStepDb = new ArrayList<>(nbZones); + this.inputSources = new ArrayList<>(nbZones); + this.isSetVolumeCommandEnabled = baseModelProperties.isSetVolumeCommandEnabled(); + this.areDbChannelsEnabled = baseModelProperties.areDbChannelsEnabled(); + this.burstMessageDelay = baseModelProperties.getBurstMessageDelay(); + this.isBurstModeEnabled = baseModelProperties.isBurstModeEnabled(); + + // Copy the properties from the source ModelProperties. + int nextZone = 1; + for (; nextZone <= baseModelProperties.getNbZones(); nextZone++) { + this.zonesVolumeMaxDb.add(baseModelProperties.getVolumeMaxDb(nextZone)); + this.zonesVolumeMinDb.add(baseModelProperties.getVolumeMinDb(nextZone)); + this.zonesVolumeStepDb.add(baseModelProperties.getVolumeStepDb(nextZone)); + this.inputSources.add(baseModelProperties.getInputSources(nextZone)); + } + + // SThen set extra zone to default values. + for (; nextZone <= nbZones; nextZone++) { + this.zonesVolumeMaxDb.add(DEFAULT_MAX_DB[nextZone - 1]); + this.zonesVolumeMinDb.add(DEFAULT_MIN_DB[nextZone - 1]); + this.zonesVolumeStepDb.add(DEFAULT_STEP_DB[nextZone - 1]); + this.inputSources.add(Lists.newArrayList(InputSource.values())); + } + } + + @Override + public boolean isSetVolumeCommandEnabled() { + return isSetVolumeCommandEnabled; + } + + public void setSetVolumeCommandEnabled(boolean isEnabled) { + this.isSetVolumeCommandEnabled = isEnabled; + } + + @Override + public double getVolumeMinDb(int zone) { + return isZoneValid(zone) ? zonesVolumeMinDb.get(zone - 1) : 0; + } + + public void setVolumeMinDb(int zone, double volumeMinDb) { + if (isZoneValid(zone)) { + zonesVolumeMinDb.set(zone - 1, volumeMinDb); + } + } + + @Override + public double getVolumeMaxDb(int zone) { + return isZoneValid(zone) ? zonesVolumeMaxDb.get(zone - 1) : 0; + } + + public void setVolumeMaxDb(int zone, double volumeMaxDb) { + if (isZoneValid(zone)) { + zonesVolumeMaxDb.set(zone - 1, volumeMaxDb); + } + } + + @Override + public double getVolumeStepDb(int zone) { + return isZoneValid(zone) ? zonesVolumeStepDb.get(zone - 1) : 0; + } + + public void setVolumeStepDb(int zone, double volumeStepDb) { + if (isZoneValid(zone)) { + zonesVolumeStepDb.set(zone - 1, volumeStepDb); + } + } + + @Override + public String getModelDescription() { + return modelDescription; + } + + public void setModelDescription(String modelDescription) { + this.modelDescription = modelDescription; + } + + @Override + public int getNbZones() { + return nbZones; + } + + @Override + public String getModelName() { + return modelName; + } + + public void setInputSources(int zone, Collection inputSources) { + if (isZoneValid(zone)) { + this.inputSources.set(zone - 1, inputSources); + } + } + + @Override + public Collection getInputSources(int zone) { + return isZoneValid(zone) ? inputSources.get(zone - 1) : new ArrayList<>(); + } + + public void setDbChannelsEnabled(boolean areDbChannelsEnabled) { + this.areDbChannelsEnabled = areDbChannelsEnabled; + } + + @Override + public boolean areDbChannelsEnabled() { + return areDbChannelsEnabled; + } + + public void setBurstMessageDelay(int burstMessageDelay) { + this.burstMessageDelay = burstMessageDelay; + } + + @Override + public int getBurstMessageDelay() { + return burstMessageDelay; + } + + public void setBusrtModeEnabled(Boolean isBurstModeEnabled) { + this.isBurstModeEnabled = isBurstModeEnabled; + } + + @Override + public boolean isBurstModeEnabled() { + return isBurstModeEnabled; + } + + /** + * Return true if the given zone is supported, else false. + * + * @param zone + * @return + */ + protected boolean isZoneValid(int zone) { + return zone > 0 && zone <= getNbZones(); + } + + /** + * Create a new {@link ModelProperties} with the same properties values as the given one, but override them if + * needed with the values present in the given configuration. + * + * @param modelProperties + * @param configuration + * @return + */ + public static ModelProperties cloneModelPropertiesAndOverrideWithConfiguration(ModelProperties modelProperties, + Configuration configuration) { + int newNbZone = modelProperties.getNbZones(); + + Object value = configuration.get(PioneerAvrBindingConstants.PARAMETER_NB_ZONES); + if (value != null) { + newNbZone = ((Number) value).intValue(); + } + + ModelPropertiesImpl newModelProperties = new ModelPropertiesImpl(modelProperties, newNbZone); + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_USE_SET_VOLUME_COMMAND); + if (value != null) { + newModelProperties.setSetVolumeCommandEnabled((Boolean) value); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_BURST_MESSAGE_DELAY); + if (value != null) { + newModelProperties.setBurstMessageDelay(((Number) value).intValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_USE_BURST_MODE); + if (value != null) { + newModelProperties.setBusrtModeEnabled((Boolean) value); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MIN_DB_ZONE1); + if (value != null) { + newModelProperties.setVolumeMinDb(1, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MAX_DB_ZONE1); + if (value != null) { + newModelProperties.setVolumeMaxDb(1, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_STEP_DB_ZONE1); + if (value != null) { + newModelProperties.setVolumeStepDb(1, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MIN_DB_ZONE2); + if (value != null) { + newModelProperties.setVolumeMinDb(2, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MAX_DB_ZONE2); + if (value != null) { + newModelProperties.setVolumeMaxDb(2, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_STEP_DB_ZONE2); + if (value != null) { + newModelProperties.setVolumeStepDb(2, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MIN_DB_ZONE3); + if (value != null) { + newModelProperties.setVolumeMinDb(3, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MAX_DB_ZONE3); + if (value != null) { + newModelProperties.setVolumeMaxDb(3, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_STEP_DB_ZONE3); + if (value != null) { + newModelProperties.setVolumeStepDb(3, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MIN_DB_ZONE4); + if (value != null) { + newModelProperties.setVolumeMinDb(4, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_MAX_DB_ZONE4); + if (value != null) { + newModelProperties.setVolumeMaxDb(4, ((Number) value).doubleValue()); + } + + value = configuration.get(PioneerAvrBindingConstants.PARAMETER_VOLUME_STEP_DB_ZONE4); + if (value != null) { + newModelProperties.setVolumeStepDb(4, ((Number) value).doubleValue()); + } + + return newModelProperties; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/DisplayInformation.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/DisplayInformation.java index 2d6394062b278..b3799b7cde685 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/DisplayInformation.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/DisplayInformation.java @@ -35,7 +35,6 @@ public class DisplayInformation { * @return */ public DisplayInformation(String responsePayload) { - volumeDisplay = false; guidIcon = false; infoText = ""; diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ParameterizedCommand.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ParameterizedCommand.java index 74e15bc0f92c9..0546c7031d433 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ParameterizedCommand.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ParameterizedCommand.java @@ -9,8 +9,10 @@ package org.openhab.binding.pioneeravr.internal.protocol; import org.apache.commons.lang.StringUtils; +import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType; import org.openhab.binding.pioneeravr.protocol.AvrCommand; import org.openhab.binding.pioneeravr.protocol.AvrConnectionException; +import org.openhab.binding.pioneeravr.protocol.AvrResponse.RepsonseType; /** * A command which accept a parameter. @@ -28,15 +30,18 @@ public class ParameterizedCommand extends SimpleCommand { */ public enum ParameterizedCommandType implements AvrCommand.CommandType { - VOLUME_SET("[0-9]{2,3}", "VL", "ZV", "YV", "HZV"), - INPUT_CHANNEL_SET("[0-9]{2}", "FN", "ZS", "ZT", "ZEA"); + VOLUME_SET(ResponseType.VOLUME_LEVEL, "[0-9]{2,3}", "VL", "ZV", "YV", "HZV"), + INPUT_CHANNEL_SET(ResponseType.INPUT_SOURCE_CHANNEL, "[0-9]{2}", "FN", "ZS", "ZT", "ZEA"); private String[] zoneCommands; private String parameterPattern; + private ResponseType expectedResponse; - private ParameterizedCommandType(String parameterPattern, String... zoneCommands) { + private ParameterizedCommandType(ResponseType expectedResponse, String parameterPattern, + String... zoneCommands) { this.zoneCommands = zoneCommands; this.parameterPattern = parameterPattern; + this.expectedResponse = expectedResponse; } @Override @@ -47,6 +52,11 @@ public String getCommand(int zone) { public String getParameterPattern() { return parameterPattern; } + + @Override + public RepsonseType getExpectedResponse() { + return expectedResponse; + } } private String parameter; diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/RequestResponseFactory.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/RequestResponseFactory.java index af4236349d418..a0ea07a94efe8 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/RequestResponseFactory.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/RequestResponseFactory.java @@ -9,8 +9,8 @@ package org.openhab.binding.pioneeravr.internal.protocol; import org.openhab.binding.pioneeravr.internal.protocol.ParameterizedCommand.ParameterizedCommandType; +import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType; import org.openhab.binding.pioneeravr.internal.protocol.SimpleCommand.SimpleCommandType; -import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection; /** * Factory that allows to build IpControl commands/responses. @@ -20,17 +20,6 @@ */ public final class RequestResponseFactory { - /** - * Return a connection to the AVR with the given host and port. - * - * @param host - * @param port - * @return - */ - public static IpAvrConnection getConnection(String host, Integer port) { - return new IpAvrConnection(host, port); - } - /** * Return a ParameterizedCommand of the type given in parameter and for the given zone. * @@ -82,4 +71,16 @@ public static Response getIpControlResponse(String responseData) { return new Response(responseData); } + /** + * Return a IpControlResponse object based on the given type, zone and parameter. + * + * @param responseType + * @param zone + * @param parameter + * @return + */ + public static Response getIpControlResponse(ResponseType responseType, int zone, String parameter) { + return new Response(responseType, zone, parameter); + } + } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/Response.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/Response.java index c912b051cab1e..115aa3c2b128b 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/Response.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/Response.java @@ -30,11 +30,17 @@ public class Response implements AvrResponse { * */ public enum ResponseType implements AvrResponse.RepsonseType { - POWER_STATE("[0-2]", "PWR", "APR", "BPR"), - VOLUME_LEVEL("[0-9]{2,3}", "VOL", "ZV", "YV"), - MUTE_STATE("[0-1]", "MUT", "Z2MUT", "Z3MUT"), - INPUT_SOURCE_CHANNEL("[0-9]{2}", "FN", "Z2F", "Z3F"), - DISPLAY_INFORMATION("[0-9a-fA-F]{30}", "FL"); + // NONE is a fake response for requests that do not expect responses or when responses should be treated as + // notifications only. + NONE(false, "", ""), + POWER_STATE(false, "[0-1]", "PWR", "APR", "BPR"), + VOLUME_LEVEL(false, "[0-9]{2,3}", "VOL", "ZV", "YV"), + MUTE_STATE(false, "[0-1]", "MUT", "Z2MUT", "Z3MUT"), + INPUT_SOURCE_CHANNEL(false, "[0-9]{2}", "FN", "Z2F", "Z3F"), + DISPLAY_INFORMATION(false, "[0-9a-fA-F]{30}", "FL"), + UNKNOWN_COMMAND(true, "", "E4"), + UNKNOWN_PARAMETER(true, "", "E6"), + GENERIC_ERROR(true, "", "R"); private String[] responsePrefixZone; @@ -42,7 +48,10 @@ public enum ResponseType implements AvrResponse.RepsonseType { private Pattern[] matchPatternZone; - private ResponseType(String parameterPattern, String... responsePrefixZone) { + private boolean isError; + + private ResponseType(boolean isError, String parameterPattern, String... responsePrefixZone) { + this.isError = isError; this.responsePrefixZone = responsePrefixZone; this.parameterPattern = parameterPattern; @@ -70,6 +79,11 @@ public String getParameterPattern() { return parameterPattern; } + @Override + public boolean isError() { + return isError; + } + @Override public Integer match(String responseData) { Integer zone = null; @@ -111,12 +125,26 @@ public String parseParameter(String responseData) { private String parameter; + public static Response getReponseNone(int zone) { + return new Response(ResponseType.NONE, zone); + } + + private Response(ResponseType responseType, int zone) { + this(responseType, zone, null); + } + + protected Response(ResponseType responseType, int zone, String parameter) { + this.responseType = responseType; + this.zone = zone; + this.parameter = parameter; + } + public Response(String responseData) throws AvrConnectionException { if (StringUtils.isEmpty(responseData)) { throw new AvrConnectionException("responseData is empty. Cannot parse the response."); } - parseResponseType(responseData); + parseResponse(responseData); if (this.responseType == null) { throw new AvrConnectionException("Cannot find the responseType of the responseData " + responseData); @@ -133,7 +161,7 @@ public Response(String responseData) throws AvrConnectionException { * @param responseData * @return */ - private void parseResponseType(String responseData) { + private void parseResponse(String responseData) { for (ResponseType responseType : ResponseType.values()) { zone = responseType.match(responseData); if (zone != null) { @@ -163,4 +191,9 @@ public Integer getZone() { return this.zone; } + @Override + public String toString() { + return "Response [responseType=" + responseType + ", zone=" + zone + ", parameter=" + parameter + "]"; + } + } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/SimpleCommand.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/SimpleCommand.java index a0fdc203dea56..4b5fe9bead96b 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/SimpleCommand.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/SimpleCommand.java @@ -8,7 +8,9 @@ */ package org.openhab.binding.pioneeravr.internal.protocol; +import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType; import org.openhab.binding.pioneeravr.protocol.AvrCommand; +import org.openhab.binding.pioneeravr.protocol.AvrResponse.RepsonseType; /** * A simple command without parameters. @@ -26,29 +28,37 @@ public class SimpleCommand implements AvrCommand { */ public enum SimpleCommandType implements AvrCommand.CommandType { - POWER_ON("PO", "APO", "BPO"), - POWER_OFF("PF", "APF", "BPF"), - POWER_QUERY("?P", "?AP", "?BP"), - VOLUME_UP("VU", "ZU", "YU"), - VOLUME_DOWN("VD", "ZD", "YD"), - VOLUME_QUERY("?V", "?ZV", "?YV"), - MUTE_ON("MO", "Z2MO", "Z3MO"), - MUTE_OFF("MF", "Z2MF", "Z3MF"), - MUTE_QUERY("?M", "?Z2M", "?Z3M"), - INPUT_CHANGE_CYCLIC("FU"), - INPUT_CHANGE_REVERSE("FD"), - INPUT_QUERY("?F", "?ZS", "?ZT"); + POWER_ON(ResponseType.NONE, "PO", "APO", "BPO"), + POWER_OFF(ResponseType.NONE, "PF", "APF", "BPF"), + POWER_QUERY(ResponseType.POWER_STATE, "?P", "?AP", "?BP"), + VOLUME_UP(ResponseType.NONE, "VU", "ZU", "YU"), + VOLUME_DOWN(ResponseType.NONE, "VD", "ZD", "YD"), + VOLUME_QUERY(ResponseType.VOLUME_LEVEL, "?V", "?ZV", "?YV"), + MUTE_ON(ResponseType.MUTE_STATE, "MO", "Z2MO", "Z3MO"), + MUTE_OFF(ResponseType.MUTE_STATE, "MF", "Z2MF", "Z3MF"), + MUTE_QUERY(ResponseType.MUTE_STATE, "?M", "?Z2M", "?Z3M"), + INPUT_CHANGE_CYCLIC(ResponseType.INPUT_SOURCE_CHANNEL, "FU"), + INPUT_CHANGE_REVERSE(ResponseType.INPUT_SOURCE_CHANNEL, "FD"), + INPUT_QUERY(ResponseType.INPUT_SOURCE_CHANNEL, "?F", "?ZS", "?ZT"), + DISPLAY_QUERY(ResponseType.DISPLAY_INFORMATION, "?FL"); private String zoneCommands[]; + private ResponseType expectedResponse; - private SimpleCommandType(String... command) { + private SimpleCommandType(ResponseType expectedResponse, String... command) { this.zoneCommands = command; + this.expectedResponse = expectedResponse; } @Override public String getCommand(int zone) { return zoneCommands[zone - 1]; } + + @Override + public RepsonseType getExpectedResponse() { + return expectedResponse; + } } private CommandType commandType; @@ -74,4 +84,9 @@ public int getZone() { return zone; } + @Override + public boolean isResponseExpected() { + return commandType.getExpectedResponse() != null && commandType.getExpectedResponse() != ResponseType.NONE; + } + } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/StreamAvrConnection.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/StreamAvrConnection.java index 409a6c3b0a3cf..53d9631c42140 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/StreamAvrConnection.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/StreamAvrConnection.java @@ -15,29 +15,30 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.SocketTimeoutException; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.xml.bind.DatatypeConverter; -import org.eclipse.smarthome.core.library.types.DecimalType; -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.library.types.StringType; -import org.eclipse.smarthome.core.types.Command; -import org.openhab.binding.pioneeravr.internal.protocol.ParameterizedCommand.ParameterizedCommandType; -import org.openhab.binding.pioneeravr.internal.protocol.SimpleCommand.SimpleCommandType; +import org.apache.commons.collections.CollectionUtils; +import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType; +import org.openhab.binding.pioneeravr.internal.util.Pair; import org.openhab.binding.pioneeravr.protocol.AvrCommand; import org.openhab.binding.pioneeravr.protocol.AvrConnection; -import org.openhab.binding.pioneeravr.protocol.CommandTypeNotSupportedException; -import org.openhab.binding.pioneeravr.protocol.event.AvrDisconnectionEvent; -import org.openhab.binding.pioneeravr.protocol.event.AvrDisconnectionListener; -import org.openhab.binding.pioneeravr.protocol.event.AvrStatusUpdateEvent; -import org.openhab.binding.pioneeravr.protocol.event.AvrUpdateListener; -import org.openhab.binding.pioneeravr.protocol.utils.VolumeConverter; +import org.openhab.binding.pioneeravr.protocol.AvrConnectionException; +import org.openhab.binding.pioneeravr.protocol.AvrResponse; +import org.openhab.binding.pioneeravr.protocol.event.AvrConnectionListener; +import org.openhab.binding.pioneeravr.protocol.event.AvrNotificationEvent; +import org.openhab.binding.pioneeravr.protocol.event.AvrNotificationListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,54 +57,123 @@ public abstract class StreamAvrConnection implements AvrConnection { private final Logger logger = LoggerFactory.getLogger(StreamAvrConnection.class); - // The maximum time to wait incoming messages. - private static final Integer READ_TIMEOUT = 1000; + /** + * Timeout when the response for a request is not received after this duration. + */ + private static final Integer RESPONSE_TIMEOUT = 500; + + /** + * The number of milliseconds between a notification is received and its processing. + */ + private static final Integer NOTIFICATION_EXECUTOR_DELAY = 250; - private List updateListeners; - private List disconnectionListeners; + private List updateListeners; + private List connectionListeners; - private IpControlInputStreamReader inputStreamReader; + private ScheduledExecutorService executorService; + private Future inputStreamReaderFuture; private DataOutputStream outputStream; - public StreamAvrConnection() { - this.updateListeners = new ArrayList<>(); - this.disconnectionListeners = new ArrayList<>(); + private Object responseLock; + private AvrCommand sentCommand; + private AvrResponse receivedResponse; + + private Map>> notificationsByType; + + private int burstMessageDelay; + private boolean isBurstModeEnabled; + + public StreamAvrConnection(ScheduledExecutorService executorService) { + this.executorService = executorService; + this.updateListeners = new CopyOnWriteArrayList<>(); + this.connectionListeners = new CopyOnWriteArrayList<>(); + this.responseLock = new Object(); + this.notificationsByType = new HashMap<>(); + this.burstMessageDelay = 0; } @Override - public void addUpdateListener(AvrUpdateListener listener) { - synchronized (updateListeners) { - updateListeners.add(listener); - } + public void addNotificationListener(AvrNotificationListener listener) { + updateListeners.add(listener); } @Override - public void addDisconnectionListener(AvrDisconnectionListener listener) { - synchronized (disconnectionListeners) { - disconnectionListeners.add(listener); + public void addConnectionListener(AvrConnectionListener listener) { + connectionListeners.add(listener); + } + + /** + * Notify the listeners with the given notification event. The event may be dropped to limit the notification + * rate. + * + * The volume level notifications may be also discarded if needed. + * + * @param event + */ + protected void notifyListeners(AvrNotificationEvent event) { + ResponseType notificationType = event.getNotification().getResponseType(); + + // Limit the rate of the notifications since it can be pretty heavy (for example, when the volume is + // modified without the SetVolume command, it can be up to 200 notifications by seconds) + // => limit to 1 notification of each type each 250 ms (keep only the last notification of each type) + synchronized (notificationsByType) { + Pair> notificationContext = notificationsByType + .remove(event.getNotification().getResponseType()); + // If pending a notification is already found for the notification type, remove it and cancel the + // notification process. + if (notificationContext != null) { + // If the notification process cannot be cancelled, it is already executed => it is an old process that + // is just being cleaned. If it has been cancelled, it is a pending notification that will be replaced. + if (notificationContext.getValue().cancel(false)) { + logger.debug("Cancelled the listeners notification for the notification {}", + notificationContext.getKey().getNotification()); + } + } + // Then add the notification and schedule the notification process. This process may be cancelled later if a + // new notification of the same type is received before its execution. + logger.debug("Scheduling the listeners notification for the notification {}", event.getNotification()); + notificationsByType.put(notificationType, Pair.of(event, scheduleEventNotificationProcess(event))); } } + /** + * Schedule the notification process of the given event in {@link #NOTIFICATION_EXECUTOR_DELAY} milliseconds. + * + * @param event the event to notify listeners with. + */ + protected ScheduledFuture scheduleEventNotificationProcess(AvrNotificationEvent event) { + return executorService.schedule(() -> { + // Notify all the listeners with this event. + logger.debug("Notify notification listeners for notification event: {}", event.getNotification()); + for (AvrNotificationListener pioneerAvrEventListener : updateListeners) { + pioneerAvrEventListener.statusUpdateReceived(event); + } + }, NOTIFICATION_EXECUTOR_DELAY, TimeUnit.MILLISECONDS); + } + @Override public boolean connect() { - - if (!isConnected()) { + boolean isConnected = isConnected(); + if (!isConnected) { try { openConnection(); // Start the inputStream reader. - inputStreamReader = new IpControlInputStreamReader(getInputStream()); - inputStreamReader.start(); + inputStreamReaderFuture = executorService.submit(new IpControlInputStreamReader(getInputStream())); // Get Output stream outputStream = new DataOutputStream(getOutputStream()); + isConnected = isConnected(); + + if (isConnected) { + notifyConnection(); + } } catch (IOException ioException) { logger.debug("Can't connect to {}. Cause: {}", getConnectionName(), ioException.getMessage()); } - } - return isConnected(); + return isConnected; } /** @@ -131,22 +201,33 @@ public boolean connect() { @Override public void close() { - if (inputStreamReader != null) { + if (inputStreamReaderFuture != null) { // This method block until the reader is really stopped. - inputStreamReader.stopReader(); - inputStreamReader = null; - logger.debug("Stream reader stopped!"); + inputStreamReaderFuture.cancel(true); + inputStreamReaderFuture = null; + logger.debug("Stream reader stopped for AVR@{}", getConnectionName()); } } + @Override + public AvrResponse sendCommand(AvrCommand ipControlCommand) throws TimeoutException { + return sendCommand(ipControlCommand, true); + } + /** - * Sends to command to the receiver. It does not wait for a reply. + * Send the given command and wait for the response if a response is expected and waitForResponse is true. Else + * return a response of type {@link ResponseType#NONE}. * - * @param ipControlCommand - * the command to send. + * If a Timeout occurs, a {@link TimeoutException} is thrown. + * + * This implementation is synchronized to serialize requests and avoid response collisions. + * + * @see AvrConnection#sendCommand(AvrCommand) **/ - protected boolean sendCommand(AvrCommand ipControlCommand) { - boolean isSent = false; + protected synchronized AvrResponse sendCommand(AvrCommand ipControlCommand, boolean waitForResponse) + throws TimeoutException { + // By default, return null (the request has not been sent) + AvrResponse response = null; if (connect()) { String command = ipControlCommand.getCommand(); try { @@ -154,134 +235,124 @@ protected boolean sendCommand(AvrCommand ipControlCommand) { logger.trace("Sending {} bytes: {}", command.length(), DatatypeConverter.printHexBinary(command.getBytes())); } - outputStream.writeBytes(command); - outputStream.flush(); - isSent = true; + // Critical section to avoid to miss the response + synchronized (responseLock) { + outputStream.writeBytes(command); + outputStream.flush(); + logger.debug("Command sent to AVR @{}: {}", getConnectionName(), command); + + if (ipControlCommand.isResponseExpected() && waitForResponse) { + // If a response is expected, wait until the response is received. + sentCommand = ipControlCommand; + try { + responseLock.wait(RESPONSE_TIMEOUT); + // If receivedResponse is null, it is a timeout + if (receivedResponse == null) { + throw new TimeoutException("No response received after " + RESPONSE_TIMEOUT + + " ms for the command " + command); + } else { + response = receivedResponse; + } + } catch (InterruptedException e) { + // If the thread is interrupted, do nothing special, just end the treatment. + } finally { + // Reset the context for next command to send. + receivedResponse = null; + sentCommand = null; + } + } else { + // NONE response if no response is expected or we do not want to wait for it. + response = Response.getReponseNone(ipControlCommand.getZone()); + } + } } catch (IOException ioException) { logger.error("Error occurred when sending command", ioException); // If an error occurs, close the connection close(); } - - logger.debug("Command sent to AVR @{}: {}", getConnectionName(), command); } - return isSent; - } - - @Override - public boolean sendPowerQuery(int zone) { - return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_QUERY, zone)); - } - - @Override - public boolean sendVolumeQuery(int zone) { - return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_QUERY, zone)); - } + if (response != null) { + logger.debug("Response received from AVR@{}: {}", getConnectionName(), response); + } else { + notifyDisconnection(new IOException("Connection failed.")); + } - @Override - public boolean sendMuteQuery(int zone) { - return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_QUERY, zone)); + return response; } @Override - public boolean sendSourceInputQuery(int zone) { - return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_QUERY, zone)); + public void sendBurstCommands(AvrCommand command, int count) { + List commands = Stream.generate(() -> command).limit(count).collect(Collectors.toList()); + sendBurstCommands(commands); } @Override - public boolean sendPowerCommand(Command command, int zone) throws CommandTypeNotSupportedException { - AvrCommand commandToSend = null; - - if (command == OnOffType.ON) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_ON, zone); - // Send the first Power ON command. - sendCommand(commandToSend); - - // According to the Pioneer Specs, the first request only wakeup the - // AVR CPU, the second one Power ON the AVR. Still according to the Pioneer Specs, the second - // request has to be delayed of 100 ms. + public void sendBurstCommands(List commands) { + for (AvrCommand avrCommand : commands) { try { - TimeUnit.MILLISECONDS.sleep(100); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - - } else if (command == OnOffType.OFF) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_OFF, zone); - } else { - throw new CommandTypeNotSupportedException("Command type not supported."); - } - - return sendCommand(commandToSend); - } - - @Override - public boolean sendVolumeCommand(Command command, int zone) throws CommandTypeNotSupportedException { - boolean commandSent = false; + // Send the commands. Wait for responses only if the burst mode is disabled. + sendCommand(avrCommand, !isBurstModeEnabled); - // The OnOffType for volume is equal to the Mute command - if (command instanceof OnOffType) { - commandSent = sendMuteCommand(command, zone); - } else { - AvrCommand commandToSend = null; - - if (command == IncreaseDecreaseType.DECREASE) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_DOWN, zone); - } else if (command == IncreaseDecreaseType.INCREASE) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_UP, zone); - } else if (command instanceof PercentType) { - String ipControlVolume = VolumeConverter - .convertFromPercentToIpControlVolume(((PercentType) command).doubleValue(), zone); - commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.VOLUME_SET, zone) - .setParameter(ipControlVolume); - } else if (command instanceof DecimalType) { - String ipControlVolume = VolumeConverter - .convertFromDbToIpControlVolume(((DecimalType) command).doubleValue(), zone); - commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.VOLUME_SET, zone) - .setParameter(ipControlVolume); - } else { - throw new CommandTypeNotSupportedException("Command type not supported."); + if (isBurstModeEnabled && burstMessageDelay > 0) { + Thread.sleep(burstMessageDelay); + } + } catch (TimeoutException e) { + // Should never happen since we do not wait for a response. + logger.error("Timeout during a burst send. It is probably a bug. Thank you to report it.", e); + } catch (InterruptedException e) { + // Nothing to do, just stop to send. } - - commandSent = sendCommand(commandToSend); } - return commandSent; } @Override - public boolean sendInputSourceCommand(Command command, int zone) throws CommandTypeNotSupportedException { - AvrCommand commandToSend = null; - - if (command == IncreaseDecreaseType.INCREASE) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_CYCLIC, zone); - } else if (command == IncreaseDecreaseType.DECREASE) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_REVERSE, zone); - } else if (command instanceof StringType) { - String inputSourceValue = ((StringType) command).toString(); - commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.INPUT_CHANNEL_SET, zone) - .setParameter(inputSourceValue); - } else { - throw new CommandTypeNotSupportedException("Command type not supported."); - } - - return sendCommand(commandToSend); + public void setBurstMessageDelay(int burstMessageDelay) { + this.burstMessageDelay = burstMessageDelay; } @Override - public boolean sendMuteCommand(Command command, int zone) throws CommandTypeNotSupportedException { - AvrCommand commandToSend = null; + public void setBurstModeEnabled(boolean burstModeEnabled) { + this.isBurstModeEnabled = burstModeEnabled; + } - if (command == OnOffType.ON) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_ON, zone); - } else if (command == OnOffType.OFF) { - commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_OFF, zone); - } else { - throw new CommandTypeNotSupportedException("Command type not supported."); - } + /** + * Notify the listeners about the disconnection with the given cause. + * + * @param cause + */ + private void notifyDisconnection(IOException cause) { + executorService.execute(new Runnable() { + @Override + public void run() { + if (CollectionUtils.isNotEmpty(connectionListeners)) { + for (AvrConnectionListener pioneerAvrDisconnectionListener : connectionListeners) { + pioneerAvrDisconnectionListener.onDisconnection(StreamAvrConnection.this, cause); + } + } else { + logger.warn("The AVR @{} is disconnected.", getConnectionName(), cause); + } + } + }); + } - return sendCommand(commandToSend); + /** + * Notify the listeners about the connection. + */ + private void notifyConnection() { + executorService.execute(new Runnable() { + @Override + public void run() { + if (CollectionUtils.isNotEmpty(connectionListeners)) { + for (AvrConnectionListener connectionListener : connectionListeners) { + connectionListener.onConnection(StreamAvrConnection.this); + } + } else { + logger.info("The AVR @{} is connected.", getConnectionName()); + } + } + }); } /** @@ -290,15 +361,12 @@ public boolean sendMuteCommand(Command command, int zone) throws CommandTypeNotS * @author Antoine Besnard * */ - private class IpControlInputStreamReader extends Thread { + private class IpControlInputStreamReader implements Runnable { private BufferedReader bufferedReader = null; private volatile boolean stopReader; - // This latch is used to block the stop method until the reader is really stopped. - private CountDownLatch stopLatch; - /** * Construct a reader that read the given inputStream * @@ -307,62 +375,59 @@ private class IpControlInputStreamReader extends Thread { */ public IpControlInputStreamReader(InputStream inputStream) { this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - this.stopLatch = new CountDownLatch(1); - - this.setDaemon(true); - this.setName("IpControlInputStreamReader-" + getConnectionName()); } @Override public void run() { try { - while (!stopReader && !Thread.currentThread().isInterrupted()) { - String receivedData = null; try { receivedData = bufferedReader.readLine(); } catch (SocketTimeoutException e) { - // Do nothing. Just happen to allow the thread to check if it has to stop. + // Do nothing. The timeout is configured to not infinitely block on read and to allow the thread + // to check if it has to stop. } - if (receivedData != null) { + if (receivedData != null && !receivedData.trim().isEmpty()) { logger.debug("Data received from AVR @{}: {}", getConnectionName(), receivedData); - AvrStatusUpdateEvent event = new AvrStatusUpdateEvent(StreamAvrConnection.this, receivedData); - synchronized (updateListeners) { - for (AvrUpdateListener pioneerAvrEventListener : updateListeners) { - pioneerAvrEventListener.statusUpdateReceived(event); + + try { + // Parse the message + AvrResponse message = RequestResponseFactory.getIpControlResponse(receivedData); + + synchronized (responseLock) { + // It is a response if a command has been sent, the message type is the same as the + // expected response type and the response is for the requested zone. + boolean isError = message.getResponseType().isError(); + boolean isSameType = sentCommand != null && message.getResponseType() + .equals(sentCommand.getCommandType().getExpectedResponse()); + boolean isSameZone = sentCommand != null && sentCommand.getZone() == message.getZone(); + boolean isResponse = isError || (isSameType && isSameZone); + + if (isResponse) { + // If it is a response, save it and notify the request sender. + receivedResponse = message; + responseLock.notify(); + } else { + // If it is not a response to a request, then it is a notification + // => Notify the listeners + AvrNotificationEvent event = new AvrNotificationEvent(StreamAvrConnection.this, + message); + notifyListeners(event); + } } + } catch (AvrConnectionException e) { + logger.debug("Message dropped. Unknown reponseType: {}", e.getMessage()); } } } - } catch (IOException e) { - logger.warn("The AVR @{} is disconnected.", getConnectionName(), e); - AvrDisconnectionEvent event = new AvrDisconnectionEvent(StreamAvrConnection.this, e); - for (AvrDisconnectionListener pioneerAvrDisconnectionListener : disconnectionListeners) { - pioneerAvrDisconnectionListener.onDisconnection(event); - } + notifyDisconnection(e); + } catch (Exception e) { + logger.error("Error during AVR @{} inputStream reading.", getConnectionName(), e); } - - // Notify the stopReader method caller that the reader is stopped. - this.stopLatch.countDown(); } - - /** - * Stop this reader. Block until the reader is really stopped. - */ - public void stopReader() { - this.stopReader = true; - try { - this.stopLatch.await(READ_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // Do nothing. The timeout is just here for safety and to be sure that the call to this method will not - // block the caller indefinitely. - // This exception should never happen. - } - } - } } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ip/IpAvrConnection.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ip/IpAvrConnection.java index b378657170ee1..7bede9ce7c173 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ip/IpAvrConnection.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/ip/IpAvrConnection.java @@ -13,6 +13,7 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; +import java.util.concurrent.ScheduledExecutorService; import org.openhab.binding.pioneeravr.internal.protocol.StreamAvrConnection; import org.slf4j.Logger; @@ -28,8 +29,9 @@ public class IpAvrConnection extends StreamAvrConnection { private final Logger logger = LoggerFactory.getLogger(IpAvrConnection.class); - /** default port for IP communication **/ - public static final int DEFAULT_IPCONTROL_PORT = 8102; + /** Possible default ports for IP communication **/ + public static final int DEFAULT_TELNET_PORT_1 = 23; + public static final int DEFAULT_TELNET_PORT_2 = 8102; /** Connection timeout in milliseconds **/ private static final int CONNECTION_TIMEOUT = 3000; @@ -42,13 +44,17 @@ public class IpAvrConnection extends StreamAvrConnection { private Socket ipControlSocket; - public IpAvrConnection(String receiverHost) { - this(receiverHost, null); + public IpAvrConnection(ScheduledExecutorService executorService, String receiverHost) { + this(executorService, receiverHost, null); } - public IpAvrConnection(String receiverHost, Integer ipControlPort) { + public IpAvrConnection(ScheduledExecutorService executorService, String receiverHost, Integer receiverPort) { + super(executorService); this.receiverHost = receiverHost; - this.receiverPort = ipControlPort != null && ipControlPort >= 1 ? ipControlPort : DEFAULT_IPCONTROL_PORT; + this.receiverPort = receiverPort != null && receiverPort >= 1 && receiverPort <= 65535 ? receiverPort + : DEFAULT_TELNET_PORT_1; + + logger.info("Using port {} for AVR@{}.", receiverPort, receiverHost); } @Override diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/serial/SerialAvrConnection.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/serial/SerialAvrConnection.java index 9c79b5003f5cb..b2f6bc793c3a7 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/serial/SerialAvrConnection.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/protocol/serial/SerialAvrConnection.java @@ -8,20 +8,21 @@ */ package org.openhab.binding.pioneeravr.internal.protocol.serial; -import gnu.io.NRSerialPort; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.ScheduledExecutorService; import org.openhab.binding.pioneeravr.internal.protocol.StreamAvrConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import gnu.io.NRSerialPort; + /** - * + * * A class that wraps the communication to a Pioneer AVR devices through a serial port - * + * * @author Antoine Besnard */ public class SerialAvrConnection extends StreamAvrConnection { @@ -34,14 +35,14 @@ public class SerialAvrConnection extends StreamAvrConnection { private NRSerialPort serialPort; - public SerialAvrConnection(String portName) { + public SerialAvrConnection(ScheduledExecutorService executorService, String portName) { + super(executorService); this.portName = portName; } @Override protected void openConnection() throws IOException { if (isPortNameExist(portName)) { - serialPort = new NRSerialPort(portName, LINK_SPEED); boolean isConnected = serialPort.connect(); @@ -59,7 +60,7 @@ protected void openConnection() throws IOException { /** * Check if the Serial with the given name exist. - * + * * @param portName * @return */ diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/AVRThingTypeProvider.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/AVRThingTypeProvider.java new file mode 100644 index 0000000000000..e0d8448855711 --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/AVRThingTypeProvider.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.thingtype; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.ThingTypeProvider; +import org.eclipse.smarthome.core.thing.type.ThingType; +import org.openhab.binding.pioneeravr.internal.channeltype.ChannelDefinitionFactory; +import org.openhab.binding.pioneeravr.internal.models.AbstractModel; +import org.openhab.binding.pioneeravr.internal.models.ConfigurableModel; +import org.openhab.binding.pioneeravr.internal.models.VSX1021; +import org.openhab.binding.pioneeravr.internal.models.VSX1120; +import org.openhab.binding.pioneeravr.internal.models.VSX1122; +import org.openhab.binding.pioneeravr.internal.models.VSX921; + +/** + * + * @author Antoine Besnard + * + */ +public class AVRThingTypeProvider implements ThingTypeProvider, ThingTypeManager { + + private ChannelDefinitionFactory channelDefinitionFactory; + + private Map thingTypesByUIDs; + private Map modelsByUIDs; + + public AVRThingTypeProvider() { + this.thingTypesByUIDs = new HashMap<>(); + this.modelsByUIDs = new HashMap<>(); + } + + public void activate() { + // Initialize all models + registerModel(new ConfigurableModel(channelDefinitionFactory)); + registerModel(new VSX921(channelDefinitionFactory)); + registerModel(new VSX1021(channelDefinitionFactory)); + registerModel(new VSX1120(channelDefinitionFactory)); + registerModel(new VSX1122(channelDefinitionFactory)); + } + + @Override + public Collection getThingTypes(Locale locale) { + return thingTypesByUIDs.values(); + } + + @Override + public ThingType getThingType(ThingTypeUID thingTypeUID, Locale locale) { + return thingTypesByUIDs.get(thingTypeUID); + } + + @Override + public AbstractModel getModelFromThingType(ThingTypeUID thingTypeUID) { + return modelsByUIDs.get(thingTypeUID); + } + + @Override + public void registerModel(AbstractModel model) { + ThingType thingType = new ThingType(model.getThingTypeUID(), null, model.getThingTypeLabel(), + model.getThingTypeDescription(), model.getChannelDefinitions(), model.getChannelGroupDefinitions(), + null, model.getThingTypeConfigDescriptionURI()); + thingTypesByUIDs.put(thingType.getUID(), thingType); + modelsByUIDs.put(thingType.getUID(), model); + } + + @Override + public Set getRegisteredThingTypesUIDs() { + return thingTypesByUIDs.keySet(); + } + + public void setChannelDefinitionFactory(ChannelDefinitionFactory channelDefinitionFactory) { + this.channelDefinitionFactory = channelDefinitionFactory; + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/ThingTypeManager.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/ThingTypeManager.java new file mode 100644 index 0000000000000..4dbff10e401cc --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/thingtype/ThingTypeManager.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.thingtype; + +import java.util.Set; + +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.type.ThingType; +import org.openhab.binding.pioneeravr.internal.models.AbstractModel; + +/** + * + * @author Antoine Besnard + * + */ +public interface ThingTypeManager { + + /** + * Return the {@link AbstractModel} from the given {@link ThingTypeUID} if it is registered. Else return null. + * + * @param thingTypeUID + * @return + */ + AbstractModel getModelFromThingType(ThingTypeUID thingTypeUID); + + /** + * Register the given {@link AbstractModel} in this manager. Create the corresponding {@link ThingType}. + * + * @param model + */ + void registerModel(AbstractModel model); + + /** + * Return all the {@link ThingTypeUID} registered in this manager. + * + * @return + */ + Set getRegisteredThingTypesUIDs(); + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/util/Pair.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/util/Pair.java new file mode 100644 index 0000000000000..f920623743fae --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/internal/util/Pair.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2017 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.internal.util; + +/** + * + * @author Antoine Besnard + * + * @param + * @param + */ +public class Pair { + + private K key; + private V value; + + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public static Pair of(K key, V value) { + return new Pair<>(key, value); + } + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrCommand.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrCommand.java index e8fc50f4726a3..02c679ace207e 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrCommand.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrCommand.java @@ -8,6 +8,8 @@ */ package org.openhab.binding.pioneeravr.protocol; +import org.openhab.binding.pioneeravr.protocol.AvrResponse.RepsonseType; + /** * The base interface for an AVR command. * @@ -17,7 +19,7 @@ public interface AvrCommand { /** - * Represent a CommandType of command requests + * Represent a CommandType of command requests. * * @author Antoine Besnard * @@ -34,11 +36,18 @@ public interface CommandType { public String getCommand(int zone); /** - * Return the name of the command type + * Return the name of the command type. * * @return */ public String name(); + + /** + * Return the expected response type to this request. May be null if no response is expected. + * + * @return + */ + public RepsonseType getExpectedResponse(); } /** @@ -62,4 +71,11 @@ public interface CommandType { */ public CommandType getCommandType(); + /** + * Return true if a response is expected for this command, else false. + * + * @return + */ + public boolean isResponseExpected(); + } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrConnection.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrConnection.java index e6cf9a1b5edd2..44b8533fa1d96 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrConnection.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrConnection.java @@ -8,9 +8,11 @@ */ package org.openhab.binding.pioneeravr.protocol; -import org.eclipse.smarthome.core.types.Command; -import org.openhab.binding.pioneeravr.protocol.event.AvrDisconnectionListener; -import org.openhab.binding.pioneeravr.protocol.event.AvrUpdateListener; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import org.openhab.binding.pioneeravr.protocol.event.AvrConnectionListener; +import org.openhab.binding.pioneeravr.protocol.event.AvrNotificationListener; /** * Represent a connection to a remote Pioneer AVR. @@ -21,110 +23,95 @@ public interface AvrConnection { /** - * Add an update listener. It is notified when an update is received from the AVR. + * Add a notification listener. It is notified when a notification is received from the AVR. * * @param listener */ - public void addUpdateListener(AvrUpdateListener listener); + void addNotificationListener(AvrNotificationListener listener); /** * Add a disconnection listener. It is notified when the AVR is disconnected. * * @param listener */ - public void addDisconnectionListener(AvrDisconnectionListener listener); + void addConnectionListener(AvrConnectionListener listener); /** * Connect to the receiver. Return true if the connection has succeeded or if already connected. * **/ - public boolean connect(); + boolean connect(); /** * Return true if this manager is connected to the AVR. * * @return */ - public boolean isConnected(); + boolean isConnected(); /** * Closes the connection. **/ - public void close(); + void close(); /** - * Send a power state query to the AVR + * Sends the command to the receiver. * - * @param zone - * @return - */ - public boolean sendPowerQuery(int zone); - - /** - * Send a volume level query to the AVR + * If a disconnection is detected during the send, the listeners are notified about the disconnection and null is + * returned. * - * @param zone - * @return - */ - public boolean sendVolumeQuery(int zone); + * @param command the command to send. + * @return the response to the command, or null if the request has not been sent. + * @throws TimeoutException if no response to the command is received after the time out delay. + **/ + AvrResponse sendCommand(AvrCommand command) throws TimeoutException; /** - * Send a mute state query to the AVR + * Sends the commands to the receiver in burst. Does not wait for the last command response before sending the next + * one. No response is expected from this burst. * - * @param zone - * @return - */ - public boolean sendMuteQuery(int zone); - - /** - * Send a source input state query to the AVR + * During the burst (until the last command is sent), all responses and notifications from the AVR will be + * discarded. It means that if the AVR sends back responses for this commands and they are received after the last + * command is sent, the responses will be discarded or considered as notifications (if the response type is a + * notification). * - * @param zone - * @return - */ - public boolean sendSourceInputQuery(int zone); + * @param command the command to send. + * @param the number of times the command will be sent. + **/ + void sendBurstCommands(AvrCommand command, int count); /** - * Send a power command ot the AVR based on the openHAB command + * Sends the commands to the receiver in burst. Does not wait for the last command response before sending the next + * one. No response is expected from this burst. * - * @param command - * @param zone - * @return - */ - public boolean sendPowerCommand(Command command, int zone) throws CommandTypeNotSupportedException; - - /** - * Send a volume command to the AVR based on the openHAB command + * During the burst (until the last command is sent), all responses and notifications from the AVR will be + * discarded. It means that if the AVR sends back responses for this commands and they are received after the last + * command is sent, the responses will be discarded or considered as notifications (if the response type is a + * notification). * - * @param command - * @param zone - * @return - */ - public boolean sendVolumeCommand(Command command, int zone) throws CommandTypeNotSupportedException; + * @param command the commands to send. + **/ + void sendBurstCommands(List commands); /** - * Send a source input selection command to the AVR based on the openHAB command + * Return the connection name. * - * @param command - * @param zone * @return */ - public boolean sendInputSourceCommand(Command command, int zone) throws CommandTypeNotSupportedException; + String getConnectionName(); /** - * Send a mute command to the AVR based on the openHAB command + * Define the delay between two messages in burst send. * - * @param command - * @param zone - * @return + * @param burstMessageDelay */ - public boolean sendMuteCommand(Command command, int zone) throws CommandTypeNotSupportedException; + void setBurstMessageDelay(int burstMessageDelay); /** - * Return the connection name + * Enable/Disable the burst mode. If disabled, the burst commands are sent in "slow" mode. * - * @return + * @param burstModeEnabled */ - public String getConnectionName(); + void setBurstModeEnabled(boolean burstModeEnabled); -} \ No newline at end of file +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrResponse.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrResponse.java index 4c23f1cb14476..0869f3bea3215 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrResponse.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/AvrResponse.java @@ -65,6 +65,13 @@ public interface RepsonseType { * @return */ public String parseParameter(String responseData); + + /** + * Return true if the response type is an error response. + * + * @return + */ + boolean isError(); } /** @@ -90,7 +97,7 @@ public interface RepsonseType { /** * Return the zone number which is concerned by this response. - * + * * @return */ public Integer getZone(); diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrConnectionListener.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrConnectionListener.java new file mode 100644 index 0000000000000..193db349a027a --- /dev/null +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrConnectionListener.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.pioneeravr.protocol.event; + +import org.openhab.binding.pioneeravr.protocol.AvrConnection; + +/** + * A listener which is notified when an AVR is connected or disconnected. + * + * @author Antoine Besnard + * + */ +public interface AvrConnectionListener { + + /** + * Called when an AVR is disconnected. + * + * @param connection + * @param cause + */ + public void onDisconnection(AvrConnection connection, Throwable cause); + + /** + * Called when an AVR is connected. + * + * @param connection + */ + public void onConnection(AvrConnection connection); + +} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionListener.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionListener.java deleted file mode 100644 index 52ac958b1d883..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2017 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.pioneeravr.protocol.event; - -/** - * A listener which is notified when an AVR is disconnected. - * - * @author Antoine Besnard - * - */ -public interface AvrDisconnectionListener { - - /** - * Called when an AVR is disconnected. - * - * @param event - */ - public void onDisconnection(AvrDisconnectionEvent event); - -} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionEvent.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationEvent.java similarity index 55% rename from addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionEvent.java rename to addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationEvent.java index e8102344c98ef..3935990ca0dcb 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrDisconnectionEvent.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationEvent.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2017 by the respective copyright holders. + * Copyright (c) 2014-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -9,29 +9,29 @@ package org.openhab.binding.pioneeravr.protocol.event; import org.openhab.binding.pioneeravr.protocol.AvrConnection; +import org.openhab.binding.pioneeravr.protocol.AvrResponse; /** - * An event fired when an AVR is disconnected. - * - * @author Antoine Besnard + * The event fired when a notification is received from the AVR. * + * @author Antoine Besnard */ -public class AvrDisconnectionEvent { +public class AvrNotificationEvent { private AvrConnection connection; - private Throwable cause; + private AvrResponse notification; - public AvrDisconnectionEvent(AvrConnection connection, Throwable cause) { + public AvrNotificationEvent(AvrConnection connection, AvrResponse notification) { this.connection = connection; - this.cause = cause; + this.notification = notification; } public AvrConnection getConnection() { return connection; } - public Throwable getCause() { - return cause; + public AvrResponse getNotification() { + return notification; } } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrUpdateListener.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationListener.java similarity index 84% rename from addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrUpdateListener.java rename to addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationListener.java index 1de8d6f289b18..7ef16a42d2cfc 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrUpdateListener.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrNotificationListener.java @@ -17,11 +17,11 @@ * @author Rainer Ostendorf * @author based on the Onkyo binding by Pauli Anttila and others */ -public interface AvrUpdateListener extends EventListener { +public interface AvrNotificationListener extends EventListener { /** * Procedure for receive status update from Pioneer receiver. */ - public void statusUpdateReceived(AvrStatusUpdateEvent event); + public void statusUpdateReceived(AvrNotificationEvent event); } diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrStatusUpdateEvent.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrStatusUpdateEvent.java deleted file mode 100644 index 7f4c812812620..0000000000000 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/event/AvrStatusUpdateEvent.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2017 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.pioneeravr.protocol.event; - -import org.openhab.binding.pioneeravr.protocol.AvrConnection; - -/** - * The event fired when a status is received from the AVR. - * - * @author Antoine Besnard - */ -public class AvrStatusUpdateEvent { - - private AvrConnection connection; - private String data; - - public AvrStatusUpdateEvent(AvrConnection connection, String data) { - this.connection = connection; - this.data = data; - } - - public AvrConnection getConnection() { - return connection; - } - - public String getData() { - return data; - } - -} diff --git a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/utils/VolumeConverter.java b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/utils/VolumeConverter.java index 676ce37f3bf18..0bde52222893a 100644 --- a/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/utils/VolumeConverter.java +++ b/addons/binding/org.openhab.binding.pioneeravr/src/main/java/org/openhab/binding/pioneeravr/protocol/utils/VolumeConverter.java @@ -10,84 +10,167 @@ import java.text.DecimalFormat; +import org.apache.commons.lang.StringUtils; +import org.openhab.binding.pioneeravr.internal.models.properties.ModelProperties; + /** * * @author Antoine Besnard - Initial contribution */ public final class VolumeConverter { - // Volume Format / MAX_IP / Min_DB for Zones - // Zone1 Command ***VL 000 to 185 (-80.0dB - +12.0dB - 1step = 0.5dB) - // Zone2 Command **ZV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) - // Zone3 Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) - // HDZone Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB) - private static final String[] IP_CONTROL_VOLUME_FORMAT = { "000", "00", "00", "00" }; - private static final String[] IP_CONTROL_VOLUME_DEFAULT_VALUE = { "000", "00", "00", "00" }; + private ModelProperties modelProperties; + + public VolumeConverter(ModelProperties modelProperties) { + this.modelProperties = modelProperties; + } + + public double getIpUnitsByDb(int zone) { + // 1-step of Zone 1: 0.5dB => 2 units by dB + // 1-step of other Zones: 1dB => 1 unit by dB + return zone == 1 ? 2 : 1; + } + + public double getStepDbVolume(int zone) { + return modelProperties.getVolumeStepDb(zone); + } - private static final double[] MAX_IP_CONTROL_VOLUME = { 184, 80, 80, 80 }; - private static final double[] MIN_DB_VOLUME = { 80, 80, 80, 80 }; + public double getMaxDbVolume(int zone) { + return modelProperties.getVolumeMaxDb(zone); + } + + public double getMinDbVolume(int zone) { + return modelProperties.getVolumeMinDb(zone); + } /** * Return the double value of the volume from the value received in the IpControl response. * * @param ipControlVolume + * @param zone * @return the volume in Db */ - public static double convertFromIpControlVolumeToDb(String ipControlVolume, int zone) { + public double convertFromIpControlVolumeToDb(String ipControlVolume, int zone) { validateZone(zone - 1); - double ipControlVolumeInt = Double.parseDouble(ipControlVolume); - return ((ipControlVolumeInt - 1d) / 2d) - MIN_DB_VOLUME[zone - 1]; + double volumePercent = convertFromIpControlVolumeToPercent(ipControlVolume, zone); + return convertFromPercentToDb(volumePercent, zone); } /** * Return the string parameter to send to the AVR based on the given volume. * * @param volumeDb + * @param zone * @return the volume for IpControlRequest */ - public static String convertFromDbToIpControlVolume(double volumeDb, int zone) { + public String convertFromDbToIpControlVolume(double volumeDb, int zone) { validateZone(zone - 1); - double ipControlVolume = ((MIN_DB_VOLUME[zone - 1] + volumeDb) * 2d) + 1d; - return formatIpControlVolume(ipControlVolume, zone); + double volumePercent = convertFromDbToPercent(volumeDb, zone); + return convertFromPercentToIpControlVolume(volumePercent, zone); + } + + /** + * Return the volume in percent from the volume in dB. + * + * @param volumeDb + * @param zone + * @return + */ + public double convertFromDbToPercent(double volumeDb, int zone) { + validateZone(zone - 1); + double maxDb = modelProperties.getVolumeMaxDb(zone); + double minDb = modelProperties.getVolumeMinDb(zone); + double volumeRange = Math.abs(minDb) + Math.abs(maxDb); + double volumeShift = Math.abs(minDb - volumeDb); + + return volumeShift * 100d / volumeRange; } /** - * Return the String parameter to send to the AVR based on the given persentage of the max volume level. + * Return the volume in dB from the volume in percent. * * @param volumePercent + * @param zone + * @return + */ + public double convertFromPercentToDb(double volumePercent, int zone) { + validateZone(zone - 1); + double maxDb = modelProperties.getVolumeMaxDb(zone); + double minDb = modelProperties.getVolumeMinDb(zone); + double volumeRange = Math.abs(minDb) + Math.abs(maxDb); + double volumeShift = volumeRange * volumePercent / 100d; + + return minDb + volumeShift; + } + + /** + * Return the String parameter to send to the AVR based on the given percentage of the max volume level. + * + * @param volumePercent + * @param zone * @return the volume for IpControlRequest */ - public static String convertFromPercentToIpControlVolume(double volumePercent, int zone) { + public String convertFromPercentToIpControlVolume(double volumePercent, int zone) { validateZone(zone - 1); - double ipControlVolume = 1 + (volumePercent * MAX_IP_CONTROL_VOLUME[zone - 1] / 100); + double ipUnitsByDb = getIpUnitsByDb(zone); + double maxDb = modelProperties.getVolumeMaxDb(zone); + double minDb = modelProperties.getVolumeMinDb(zone); + double maxIpControlVolume = ((maxDb - minDb) * ipUnitsByDb) + 1d; + double ipControlVolume = volumePercent * maxIpControlVolume / 100; return formatIpControlVolume(ipControlVolume, zone); } /** - * Return the percentage of the max volume levelfrom the value received in the IpControl response. + * Return the percentage of the max volume level from the value received in the IpControl response. * * @param ipControlVolume + * @param zone * @return the volume percentage */ - public static double convertFromIpControlVolumeToPercent(String ipControlVolume, int zone) { + public double convertFromIpControlVolumeToPercent(String ipControlVolume, int zone) { validateZone(zone - 1); double ipControlVolumeInt = Double.parseDouble(ipControlVolume); - return ((ipControlVolumeInt - 1d) * 100d) / MAX_IP_CONTROL_VOLUME[zone - 1]; + double ipUnitsByDb = getIpUnitsByDb(zone); + double maxDb = modelProperties.getVolumeMaxDb(zone); + double minDb = modelProperties.getVolumeMinDb(zone); + double maxIpControlVolume = ((maxDb - minDb) * ipUnitsByDb) + 1d; + return (ipControlVolumeInt * 100d) / maxIpControlVolume; + } + + /** + * Return the formatter to use for the given zone. + * + * @return + */ + public DecimalFormat getIpControlVolumeFormatter(int zone) { + validateZone(zone); + double maxDb = modelProperties.getVolumeMaxDb(zone); + double minDb = modelProperties.getVolumeMinDb(zone); + double stepDb = modelProperties.getVolumeStepDb(zone); + int maxIpControlValue = Double.valueOf((maxDb - minDb) * stepDb).intValue(); + + // Format is of type "000" where there are a number of 0 equals to the number of digits of the max value of the + // ipControl volume for the given zone. + DecimalFormat decimalFormat = new DecimalFormat( + StringUtils.repeat("0", (int) (Math.log10(maxIpControlValue) + 1))); + + return decimalFormat; } /** * Format the given double value to an IpControl volume. * * @param ipControlVolume + * @param zone * @return */ - private static String formatIpControlVolume(double ipControlVolume, int zone) { + public String formatIpControlVolume(double ipControlVolume, int zone) { validateZone(zone - 1); - DecimalFormat FORMATTER = new DecimalFormat(IP_CONTROL_VOLUME_FORMAT[zone - 1]); - String result = IP_CONTROL_VOLUME_DEFAULT_VALUE[zone - 1]; + DecimalFormat formatter = new DecimalFormat("000"); + String result = ""; // DecimalFormat is not ThreadSafe - synchronized (FORMATTER) { - result = FORMATTER.format(Math.round(ipControlVolume)); + synchronized (formatter) { + result = formatter.format(Math.round(ipControlVolume)); } return result; }