added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.onkyo</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,309 @@
# Onkyo Binding
This binding integrates the Onkyo AV receivers.
## Introduction
Binding should be compatible with Onkyo AV receivers which support ISCP (Integra Serial Control Protocol) over Ethernet (eISCP).
## Supported Things
This binding supports only one thing: The Onkyo AV receiver.
All supported Onkyo devices are registered as an audio sink in the framework.
## Discovery
This binding can discover the supported Onkyo AV receivers.
At the moment only the following models are supported:
- HT-RC560
- TX-NR414
- TX-NR474
- TX-NR509
- TX-NR515
- TX-NR525
- TX-NR535
- TX-NR545
- TX-NR555
- TX-NR575
- TX-NR575E
- TX-NR616
- TX-NR626
- TX-NR636
- TX-NR646
- TX-NR656
- TX-NR676
- TX-NR686
- TX-NR708
- TX-NR717
- TX-NR727
- TX-NR737
- TX-NR747
- TX-NR757
- TX-NR809
- TX-NR818
- TX-NR828
- TX-NR838
- TX-NR3007
- TX-RZ900
## Binding Configuration
The binding can auto-discover the Onkyo 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 onkyo.cfg with the following content:
```text
org.openhab.onkyo:enableAutoDiscovery=false
```
This configuration parameter only controls the Onkyo AVR auto-discovery process, not the openHAB auto-discovery.
Moreover, if the openHAB auto-discovery is disabled, the Onkyo AVR auto-discovery is disabled too.
The binding has the following configuration options, which can be set for "binding:onkyo":
| Parameter | Name | Description | Required |
|-------------|--------------|--------------------------------------------------------------------------|----------|
| callbackUrl | Callback URL | URL to use for playing notification sounds, e.g. <http://192.168.0.2:8080> | no |
When an Onkyo receiver is used as an audio sink, the receiver connects to openHAB to get the audio stream.
By default, the binding sends the URL for getting the audio stream based on the Primary Address (Network Settings configuration) and the openHAB HTTP port.
Sometimes it is necessary to use the Callback URL to override the default, such as when using a reverse proxy or with some Docker implementations.
## Thing Configuration
The Onkyo AVR thing requires the ip address and the port to access it on.
In the code `avr-livingroom` refers to the user defined unique id of your Onkyo device.
A second device could be called avr2.
In the thing file, this looks e.g. like
Model specific
```text
onkyo:TX-NR818:avr-livingroom [ipAddress="192.168.1.100", port=60128]
```
or
Generic model
```text
onkyo:onkyoAVR:avr-livingroom [ipAddress="192.168.1.100", port=60128]
```
Optionally you can specify the refresh interval by `refreshInterval` parameter.
```text
onkyo:onkyoAVR:avr-livingroom [ipAddress="192.168.1.100", port=60128, refreshInterval=30]
```
Maximum volume level can also be configured by `volumeLimit` parameter.
This prevent setting receiver volume level too high, which could damage your speakers or receiver.
```text
onkyo:onkyoAVR:avr-livingroom [ipAddress="192.168.1.100", port=60128, volumeLimit=50]
```
Binding then automatically scale the volume level in both directions (100% = 50 = 100%).
You can also change the way volume scaling works.
This can be necessary if your receiver uses a different scaling system than 0-100.
You can specify a decimal number that acts as the coefficient for scaling.
See below for a few examples:
| Value | Description | Value for 100% |
|--------|------------------------------------------------------|------------------|
| 1 | Default, don't scale | 100 |
| 2 | For receivers that support 0.5 increments in volume | 200 |
| 0.8 | For receivers that go from 0-80 | 80 |
| 0.5 | For receivers that go from 0-50 | 50 |
Note that this is applied after the volume limiting took place.
```text
onkyo:onkyoAVR:avr-livingroom [ipAddress="192.168.1.100", port=60128, volumeScale=2]
```
The binding will send value 200 for maximum volume to the receiver.
## Channels
The Onkyo AVR supports the following channels (some channels are model specific):
| Channel Type ID | Item Type | Description |
|---------------------------|-----------|-----------------------------------------------------------------------------------------------------------------|
| zone1#power | Switch | Power on/off your device |
| zone1#mute | Switch | Mute/unmute zone 1 |
| zone1#input | Number | The input for zone 1 |
| zone1#volume | Dimmer | Volume of zone 1 |
| zone2#power | Switch | Power on/off zone 2 |
| zone2#mute | Switch | Mute/unmute zone 2 |
| zone2#input | Number | The input for zone 2 |
| zone2#volume | Dimmer | Volume of zone 2 |
| zone3#power | Switch | Power on/off zone 3 |
| zone3#mute | Switch | Mute/unmute zone 3 |
| zone3#input | Number | The input for zone 3 |
| zone3#volume | Dimmer | Volume of zone 3 |
| player#control | Player | Control the Zone Player, e.g. play/pause/next/previous/ffward/rewind (available if playing from Network or USB) |
| player#title | String | Title of the current song (available if playing from Network or USB) |
| player#album | String | Album name of the current song (available if playing from Network or USB) |
| player#artist | String | Artist name of the current song (available if playing from Network or USB) |
| player#currentPlayingTime | String | Current playing time of the current song (available if playing from Network or USB) |
| player#listenmode | Number | Current listening mode e.g. Stereo, 5.1ch Surround, ... |
| player#playuri | String | Plays the URI provided to the channel |
| player#albumArt | Image | Image of the current album art of the current song |
| player#albumArtUrl | String | URL to the current album art of the current song |
| netmenu#title | String | Title of the current NET service |
| netmenu#control | String | Control the USB/Net Menu, e.g. Up/Down/Select/Back/PageUp/PageDown/Select&lsqb0-9&rsqb |
| netmenu#selection | Number | The number of the currently selected USB/Net Menu entry (0-9) |
| netmenu#item0 | String | The text of USB/Net Menu entry 0 |
| netmenu#item1 | String | The text of USB/Net Menu entry 1 |
| netmenu#item2 | String | The text of USB/Net Menu entry 2 |
| netmenu#item3 | String | The text of USB/Net Menu entry 3 |
| netmenu#item4 | String | The text of USB/Net Menu entry 4 |
| netmenu#item5 | String | The text of USB/Net Menu entry 5 |
| netmenu#item6 | String | The text of USB/Net Menu entry 6 |
| netmenu#item7 | String | The text of USB/Net Menu entry 7 |
| netmenu#item8 | String | The text of USB/Net Menu entry 8 |
| netmenu#item9 | String | The text of USB/Net Menu entry 9 |
## Rule Actions
This binding includes a rule action which allows to send raw eISCP messages to the receiver.
The rule action can be used to send commands to the receiver that are not supported by channels.
There is a separate instance for each receiver, which can be retrieved through
```
val onkyoActions = getActions("onkyo","onkyo:onkyoAVR:avr-livingroom")
```
where the first parameter always has to be `onkyo` and the second (`onkyo:onkyoAVR:avr-livingroom`) is the Thing UID of the broker that should be used.
Once this action instance is retrieved, you can invoke the `onkyoActions.sendRawCommand(String action, String value)` method on it:
```
onkyoActions.sendRawCommand("CTL", "UP")
```
This command for instance increases the volume for the center channel.
For a description of all commands you can e.g. search [this GitHub project](https://github.com/miracle2k/onkyo-eiscp/tree/master/commands/main).
Also note that when sending multiple commands there has to be a `Thread::sleep(100)` in between the commands because the action does not wait for a response from the receiver.
## Input Source Mapping
Here after are the ID values of the input sources:
- 00: DVR/VCR
- 01: SATELLITE/CABLE
- 02: GAME
- 03: AUX
- 04: GAME
- 05: PC
- 16: BLURAY/DVD
- 32: TAPE1
- 33: TAPE2
- 34: PHONO
- 35: CD
- 36: FM
- 37: AM
- 38: TUNER
- 39: MUSICSERVER
- 40: INTERNETRADIO
- 41: USB
- 42: USB_BACK
- 43: NETWORK
- 45: AIRPLAY
- 48: MULTICH
- 50: SIRIUS
## Item Configuration
demo.items
```java
Switch avrLrZ1_Power "Power" <switch> { channel="onkyo:onkyoAVR:avr-livingroom:zone1#power" }
Switch avrLrZ1_Mute "Mute" <soundvolume> { channel="onkyo:onkyoAVR:avr-livingroom:zone1#mute" }
Number avrLrZ1_Input "Input [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:zone1#input" }
Dimmer avrLrZ1_Volume "Volume [%d]" <soundvolume> { channel="onkyo:onkyoAVR:avr-livingroom:zone1#volume" }
Switch avrLrZ2_Power "Power [%s]" <switch> { channel="onkyo:onkyoAVR:avr-livingroom:zone2#power" }
Switch avrLrZ2_Mute "Mute [%s]" { channel="onkyo:onkyoAVR:avr-livingroom:zone2#mute" }
Number avrLrZ2_Input "Input [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:zone2#input" }
Dimmer avrLrZ2_Volume "Volume [%s]" <soundvolume> { channel="onkyo:onkyoAVR:avr-livingroom:zone2#volume" }
Player avrLrPlayer_Control "Control" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#control" }
String avrLrPlayer_Title "Title [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#title" }
String avrLrPlayer_Album "Album [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#album" }
String avrLrPlayer_Artist "Artist [%s]" <parents_2_5> { channel="onkyo:onkyoAVR:avr-livingroom:player#artist" }
String avrLrPlayer_CurrentPlayingTime "CurrentPlayingTime [%s]" <clock> { channel="onkyo:onkyoAVR:avr-livingroom:player#currentPlayingTime" }
Number avrLrPlayer_Listenmode "Listenmode [%d]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#listenmode" }
String avrLrPlayer_PlayURI "PlayURI [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#playuri" }
Image avrLrPlayer_AlbumArt "AlbumArt [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#albumArt" }
String avrLrPlayer_AlbumArtUrl "AlbumArtURL [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:player#albumArtUrl" }
String avrLrNet_Title "Title [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#title" }
String avrLrNet_Control "Control" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#control" }
Number avrLrNet_Selection "Selection [%d]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#selection" }
String avrLrNet_Item0 "Item0 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item0" }
String avrLrNet_Item1 "Item1 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item1" }
String avrLrNet_Item2 "Item2 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item2" }
String avrLrNet_Item3 "Item3 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item3" }
String avrLrNet_Item4 "Item4 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item4" }
String avrLrNet_Item5 "Item5 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item5" }
String avrLrNet_Item6 "Item6 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item6" }
String avrLrNet_Item7 "Item7 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item7" }
String avrLrNet_Item8 "Item8 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item8" }
String avrLrNet_Item9 "Item9 [%s]" <text> { channel="onkyo:onkyoAVR:avr-livingroom:netmenu#item9" }
```
## Sitemap Configuration
demo.sitemap
```perl
sitemap demo label="Onkyo AVR"
{
Frame label="Zone1" {
Switch item=avrLrZ1_Power
Switch item=avrLrZ1_Mute
Selection item=avrLrZ1_Input mappings=[ 0='DVR/VCR', 1='SATELLITE/CABLE', 2='GAME', 3='AUX', 4='GAME', 5='PC', 16='BLURAY/DVD', 32='TAPE1', 33='TAPE2', 34='PHONO', 35='CD', 36='FM', 37='AM', 38='TUNER', 39='MUSICSERVER', 40='INTERNETRADIO', 41='USB', 42='USB_BACK', 43='NETWORK', 45='AIRPLAY', 48='MULTICH', 50='SIRIUS' ]
Slider item=avrLrZ1_Volume
}
Frame label="Zone 2" {
Switch item=avrLrZ2_Power
Switch item=avrLrZ2_Mute
Selection item=avrLrZ2_Input mappings=[ 0='DVR/VCR', 1='SATELLITE/CABLE', 2='GAME', 3='AUX', 4='GAME', 5='PC', 16='BLURAY/DVD', 32='TAPE1', 33='TAPE2', 34='PHONO', 35='CD', 36='FM', 37='AM', 38='TUNER', 39='MUSICSERVER', 40='INTERNETRADIO', 41='USB', 42='USB_BACK', 43='NETWORK', 45='AIRPLAY', 48='MULTICH', 50='SIRIUS' ]
Slider item=avrLrZ2_Volume
}
Frame label="Player" {
Default item=avrLrPlayer_Control
Text item=avrLrPlayer_Title
Text item=avrLrPlayer_Album
Text item=avrLrPlayer_Artist
Text item=avrLrPlayer_CurrentPlayingTime
Selection item=avrLrPlayer_Listenmode mappings=[0=Stereo, 1=Direct, 2=Surround, 15=Mono, 31="Whole House", 66="THX Cinema"]
}
Frame label="NetMenu" {
Text item=avrLrNet_Title
Selection item=avrLrNet_Control mappings=[ Up='Up', Down='Down', Select='Select', Back='Back', PageUp='PageUp', PageDown='PageDow', Select0='Select0', Select1='Select1', Select2='Select2', Select3='Select3', Select4='Select4', Select5='Select5', Select6='Select6', Select7='Select7', Select8='Select8', Select9='Select9' ]
Selection item=avrLrNet_Selection mappings=[ 0='Item0', 1='Item1', 2='Item2', 3='Item3', 4='Item4', 5='Item5', 6='Item6', 7='Item7', 8='Item8', 9='Item9' ]
Text item=avrLrNet_Item0
Text item=avrLrNet_Item1
Text item=avrLrNet_Item2
Text item=avrLrNet_Item3
Text item=avrLrNet_Item4
Text item=avrLrNet_Item5
Text item=avrLrNet_Item6
Text item=avrLrNet_Item7
Text item=avrLrNet_Item8
Text item=avrLrNet_Item9
}
}
```
## Audio Support
All supported Onkyo AVRs are registered as an audio sink in the framework.
Audio streams are sent to the `playuri` channel.

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.onkyo</artifactId>
<name>openHAB Add-ons :: Bundles :: Onkyo Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.onkyo-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-onkyo" description="Onkyo Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.onkyo/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,239 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to handle Onkyo Album Arts.
*
* @author Pauli Anttila - Initial contribution
* @author Stewart Cossey - fixed bug in getAlbumArt function
*/
public class OnkyoAlbumArt {
private final Logger logger = LoggerFactory.getLogger(OnkyoAlbumArt.class);
private enum State {
INVALID,
NOTSTARTED,
STARTED,
NEXT,
READY
}
private enum ImageType {
BMP,
JPEG,
URL,
NONE,
UNKNOWN
}
private StringBuilder albumArtStringBuilder = new StringBuilder();
private ImageType imageType = ImageType.UNKNOWN;
private State state = State.NOTSTARTED;
private String coverArtUrl;
public boolean isAlbumCoverTransferStarted() {
return state == State.STARTED;
}
public boolean isAlbumCoverReady() {
return state == State.READY;
}
public void clearAlbumArt() {
albumArtStringBuilder.setLength(0);
imageType = ImageType.UNKNOWN;
state = State.NOTSTARTED;
coverArtUrl = null;
}
public void addFrame(String data) {
if (data.length() <= 2) {
return;
}
char imgType = data.charAt(0);
imageType = getImageType(imgType);
char packetFlag = data.charAt(1);
String packetFlagStr = "unknown";
switch (packetFlag) {
case '0':
if (state == State.NOTSTARTED || state == State.INVALID) {
state = State.STARTED;
} else {
state = State.INVALID;
}
packetFlagStr = "Start";
albumArtStringBuilder.setLength(0);
break;
case '1':
packetFlagStr = "Next";
if (state == State.STARTED || state == State.NEXT) {
state = State.NEXT;
} else {
state = State.INVALID;
}
break;
case '2':
packetFlagStr = "End";
if (state == State.STARTED || state == State.NEXT) {
state = State.READY;
} else {
state = State.INVALID;
}
break;
case '-':
packetFlagStr = "notUsed";
state = State.READY;
break;
default:
state = State.INVALID;
logger.debug("Unknown album art packet flag '{}'", packetFlag);
}
if (state != State.INVALID) {
switch (imageType) {
case BMP:
case JPEG:
String picData = data.substring(2, data.length());
logger.debug("Received album art fragment in '{}' format, packet flag '{}', picData '{}'",
imageType, packetFlagStr, picData);
albumArtStringBuilder.append(picData);
break;
case URL:
coverArtUrl = data.substring(2);
logger.debug("Received album art url '{}'", coverArtUrl);
break;
case NONE:
logger.debug("Received information: album art not available");
break;
default:
}
} else {
logger.debug("Received album art fragment in wrong order, format '{}', packet flag '{}'", imageType,
packetFlagStr);
}
}
public byte[] getAlbumArt() throws IllegalArgumentException {
byte[] data = null;
if (state == State.READY) {
switch (imageType) {
case BMP:
case JPEG:
data = HexUtils.hexToBytes(albumArtStringBuilder.toString());
break;
case URL:
data = downloadAlbumArt(coverArtUrl);
// Workaround firmware bug providing incorrect headers causing them to be seen as body instead.
if (data != null) {
int bodyLength = data.length;
int i = new String(data).indexOf("image/");
if (i > 0) {
while (i < bodyLength && (data[i] != '\r' && data[i] != '\n')) {
i++;
}
while (i < bodyLength && (data[i] == '\r' || data[i] == '\n')) {
i++;
}
data = Arrays.copyOfRange(data, i, bodyLength);
logger.trace("Onkyo fixed picture data @ {}: {} ", i, new String(data));
}
}
break;
case NONE:
default:
}
return data;
}
throw new IllegalArgumentException("Illegal Album Art");
}
private byte[] downloadAlbumArt(String albumArtUrl) {
try {
URL url = new URL(albumArtUrl);
URLConnection connection = url.openConnection();
InputStream inputStream = connection.getInputStream();
try {
return IOUtils.toByteArray(inputStream);
} finally {
IOUtils.closeQuietly(inputStream);
}
} catch (MalformedURLException e) {
logger.warn("Album Art download failed from url '{}', reason {}", albumArtUrl, e.getMessage());
} catch (IOException e) {
logger.warn("Album Art download failed from url '{}', reason {}", albumArtUrl, e.getMessage());
}
return null;
}
private ImageType getImageType(char imgType) {
ImageType it = ImageType.UNKNOWN;
switch (imgType) {
case '0':
it = ImageType.BMP;
break;
case '1':
it = ImageType.JPEG;
break;
case '2':
it = ImageType.URL;
break;
case 'n':
it = ImageType.NONE;
break;
default:
it = ImageType.UNKNOWN;
}
return it;
}
public @NonNull String getAlbumArtMimeType() {
String mimeType = "";
switch (imageType) {
case BMP:
mimeType = "image/bmp";
break;
case JPEG:
mimeType = "image/jpeg";
break;
case UNKNOWN:
case URL:
case NONE:
default:
break;
}
return mimeType;
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link OnkyoBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Paul Frank - Initial contribution
* @author Pauli Anttila - update for openhab 2
* @author Stewart Cossey - add additional receiver models
* @author Wouter Born - Enumerate supported models using OnkyoModel enum
*/
@NonNullByDefault
public class OnkyoBindingConstants {
public static final String BINDING_ID = "onkyo";
// List of Thing Type UIDs
public static final ThingTypeUID THING_TYPE_ONKYOAV = new ThingTypeUID(BINDING_ID, "onkyoAVR");
public static final ThingTypeUID THING_TYPE_ONKYO_UNSUPPORTED = new ThingTypeUID(BINDING_ID, "onkyoUnsupported");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.concat(Stream.of(THING_TYPE_ONKYOAV, THING_TYPE_ONKYO_UNSUPPORTED),
Arrays.stream(OnkyoModel.values()).map(model -> new ThingTypeUID(BINDING_ID, model.getId())))
.collect(Collectors.toSet());
// List of thing parameters names
public static final String HOST_PARAMETER = "ipAddress";
public static final String TCP_PORT_PARAMETER = "port";
public static final String UDN_PARAMETER = "udn";
public static final String REFRESH_INTERVAL = "refreshInterval";
// List of all Channel ids
public static final String CHANNEL_POWER = "zone1#power";
public static final String CHANNEL_INPUT = "zone1#input";
public static final String CHANNEL_MUTE = "zone1#mute";
public static final String CHANNEL_VOLUME = "zone1#volume";
public static final String CHANNEL_POWERZONE2 = "zone2#power";
public static final String CHANNEL_INPUTZONE2 = "zone2#input";
public static final String CHANNEL_MUTEZONE2 = "zone2#mute";
public static final String CHANNEL_VOLUMEZONE2 = "zone2#volume";
public static final String CHANNEL_POWERZONE3 = "zone3#power";
public static final String CHANNEL_INPUTZONE3 = "zone3#input";
public static final String CHANNEL_MUTEZONE3 = "zone3#mute";
public static final String CHANNEL_VOLUMEZONE3 = "zone3#volume";
public static final String CHANNEL_CONTROL = "player#control";
public static final String CHANNEL_CURRENTPLAYINGTIME = "player#currentPlayingTime";
public static final String CHANNEL_ARTIST = "player#artist";
public static final String CHANNEL_TITLE = "player#title";
public static final String CHANNEL_ALBUM = "player#album";
public static final String CHANNEL_ALBUM_ART = "player#albumArt";
public static final String CHANNEL_ALBUM_ART_URL = "player#albumArtUrl";
public static final String CHANNEL_LISTENMODE = "player#listenmode";
public static final String CHANNEL_PLAY_URI = "player#playuri";
public static final String CHANNEL_NET_MENU_TITLE = "netmenu#title";
public static final String CHANNEL_NET_MENU_CONTROL = "netmenu#control";
public static final String CHANNEL_NET_MENU_SELECTION = "netmenu#selection";
public static final String CHANNEL_NET_MENU0 = "netmenu#item0";
public static final String CHANNEL_NET_MENU1 = "netmenu#item1";
public static final String CHANNEL_NET_MENU2 = "netmenu#item2";
public static final String CHANNEL_NET_MENU3 = "netmenu#item3";
public static final String CHANNEL_NET_MENU4 = "netmenu#item4";
public static final String CHANNEL_NET_MENU5 = "netmenu#item5";
public static final String CHANNEL_NET_MENU6 = "netmenu#item6";
public static final String CHANNEL_NET_MENU7 = "netmenu#item7";
public static final String CHANNEL_NET_MENU8 = "netmenu#item8";
public static final String CHANNEL_NET_MENU9 = "netmenu#item9";
// Used for Discovery service
public static final String MANUFACTURER = "ONKYO";
public static final String UPNP_DEVICE_TYPE = "MediaRenderer";
}

View File

@@ -0,0 +1,391 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.io.IOUtils;
import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
import org.openhab.binding.onkyo.internal.eiscp.EiscpException;
import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
import org.openhab.binding.onkyo.internal.eiscp.EiscpProtocol;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class open a TCP/IP connection to the Onkyo device and send a command.
*
* @author Pauli Anttila - Initial contribution
*/
public class OnkyoConnection {
private final Logger logger = LoggerFactory.getLogger(OnkyoConnection.class);
/** default eISCP port. **/
public static final int DEFAULT_EISCP_PORT = 60128;
/** Connection timeout in milliseconds **/
private static final int CONNECTION_TIMEOUT = 5000;
/** Connection test interval in milliseconds **/
private static final int CONNECTION_TEST_INTERVAL = 60000;
/** Socket timeout in milliseconds **/
private static final int SOCKET_TIMEOUT = CONNECTION_TEST_INTERVAL + 10000;
/** Connection retry count on error situations **/
private static final int FAST_CONNECTION_RETRY_COUNT = 3;
/** Connection retry delays in milliseconds **/
private static final int FAST_CONNECTION_RETRY_DELAY = 1000;
private static final int SLOW_CONNECTION_RETRY_DELAY = 60000;
private String ip;
private int port;
private Socket eiscpSocket;
private DataListener dataListener;
private DataOutputStream outStream;
private DataInputStream inStream;
private boolean connected;
private List<OnkyoEventListener> listeners = new ArrayList<>();
private int retryCount = 1;
private ConnectionSupervisor connectionSupervisor;
public OnkyoConnection(String ip) {
this.ip = ip;
this.port = DEFAULT_EISCP_PORT;
}
public OnkyoConnection(String ip, int port) {
this.ip = ip;
this.port = port;
}
/**
* Open connection to the Onkyo device.
*/
public void openConnection() {
connectSocket();
}
/**
* Closes the connection to the Onkyo device.
*/
public void closeConnection() {
closeSocket();
}
public void addEventListener(OnkyoEventListener listener) {
this.listeners.add(listener);
}
public void removeEventListener(OnkyoEventListener listener) {
this.listeners.remove(listener);
}
public String getConnectionName() {
return ip + ":" + port;
}
public boolean isConnected() {
return connected;
}
/**
* Sends a command to Onkyo device.
*
* @param cmd eISCP command to send
*/
public void send(final String cmd, final String value) {
try {
sendCommand(new EiscpMessage.MessageBuilder().command(cmd).value(value).build());
} catch (Exception e) {
logger.warn("Could not send command to device on {}:{}: ", ip, port, e);
}
}
private void sendCommand(EiscpMessage msg) {
logger.debug("Send command: {} to {}:{} ({})", msg.toString(), ip, port, eiscpSocket);
sendCommand(msg, retryCount);
}
/**
* Sends to command to the receiver.
*
* @param eiscpCmd the eISCP command to send.
* @param retry retry count when connection fails.
*/
private void sendCommand(EiscpMessage msg, int retry) {
if (connectSocket()) {
try {
String data = EiscpProtocol.createEiscpPdu(msg);
if (logger.isTraceEnabled()) {
logger.trace("Sending {} bytes: {}", data.length(), HexUtils.bytesToHex(data.getBytes()));
}
outStream.writeBytes(data);
outStream.flush();
} catch (IOException ioException) {
logger.warn("Error occurred when sending command: {}", ioException.getMessage());
if (retry > 0) {
logger.debug("Retry {}...", retry);
closeSocket();
sendCommand(msg, retry - 1);
} else {
sendConnectionErrorEvent(ioException.getMessage());
}
}
}
}
/**
* Connects to the receiver by opening a socket connection through the
* IP and port.
*/
private synchronized boolean connectSocket() {
if (eiscpSocket == null || !connected || !eiscpSocket.isConnected()) {
try {
// Creating a socket to connect to the server
eiscpSocket = new Socket();
// start connection tester
if (connectionSupervisor == null) {
connectionSupervisor = new ConnectionSupervisor(CONNECTION_TEST_INTERVAL);
}
eiscpSocket.connect(new InetSocketAddress(ip, port), CONNECTION_TIMEOUT);
logger.debug("Connected to {}:{}", ip, port);
// Get Input and Output streams
outStream = new DataOutputStream(eiscpSocket.getOutputStream());
inStream = new DataInputStream(eiscpSocket.getInputStream());
eiscpSocket.setSoTimeout(SOCKET_TIMEOUT);
outStream.flush();
connected = true;
// start status update listener
if (dataListener == null) {
dataListener = new DataListener();
dataListener.start();
}
} catch (UnknownHostException unknownHost) {
logger.debug("You are trying to connect to an unknown host: {}", unknownHost.getMessage());
sendConnectionErrorEvent(unknownHost.getMessage());
} catch (IOException ioException) {
logger.debug("Can't connect: {}", ioException.getMessage());
sendConnectionErrorEvent(ioException.getMessage());
}
}
return connected;
}
/**
* Closes the socket connection.
*
* @return true if the closed successfully
*/
private boolean closeSocket() {
try {
if (dataListener != null) {
dataListener.setInterrupted(true);
dataListener = null;
logger.debug("closed data listener!");
}
if (connectionSupervisor != null) {
connectionSupervisor.stopConnectionTester();
connectionSupervisor = null;
logger.debug("closed connection tester!");
}
if (inStream != null) {
IOUtils.closeQuietly(inStream);
inStream = null;
logger.debug("closed input stream!");
}
if (outStream != null) {
IOUtils.closeQuietly(outStream);
outStream = null;
logger.debug("closed output stream!");
}
if (eiscpSocket != null) {
IOUtils.closeQuietly(eiscpSocket);
eiscpSocket = null;
logger.debug("closed socket!");
}
connected = false;
} catch (Exception e) {
logger.debug("Closing connection throws an exception, {}", e.getMessage());
}
return connected;
}
/**
* This method wait any state messages form receiver.
*
* @throws IOException
* @throws InterruptedException
* @throws EiscpException
*/
private void waitStateMessages() throws NumberFormatException, IOException, InterruptedException, EiscpException {
if (connected) {
logger.trace("Waiting status messages");
while (true) {
EiscpMessage message = EiscpProtocol.getNextMessage(inStream);
sendMessageEvent(message);
}
} else {
throw new IOException("Not Connected to Receiver");
}
}
private class DataListener extends Thread {
private boolean interrupted = false;
DataListener() {
}
public void setInterrupted(boolean interrupted) {
this.interrupted = interrupted;
this.interrupt();
}
@Override
public void run() {
logger.debug("Data listener started");
boolean restartConnection = false;
long connectionAttempts = 0;
// as long as no interrupt is requested, continue running
while (!interrupted) {
try {
waitStateMessages();
connectionAttempts = 0;
} catch (EiscpException e) {
logger.debug("Error occurred during message waiting: {}", e.getMessage());
} catch (SocketTimeoutException e) {
logger.debug("No data received during supervision interval ({} ms)!", SOCKET_TIMEOUT);
restartConnection = true;
} catch (Exception e) {
if (!interrupted && !this.isInterrupted()) {
logger.debug("Error occurred during message waiting: {}", e.getMessage());
restartConnection = true;
// sleep a while, to prevent fast looping if error situation is permanent
if (++connectionAttempts < FAST_CONNECTION_RETRY_COUNT) {
mysleep(FAST_CONNECTION_RETRY_DELAY);
} else {
// slow down after few faster attempts
if (connectionAttempts == FAST_CONNECTION_RETRY_COUNT) {
logger.debug(
"Connection failed {} times to {}:{}, slowing down automatic connection to {} seconds.",
FAST_CONNECTION_RETRY_COUNT, ip, port, SLOW_CONNECTION_RETRY_DELAY / 1000);
}
mysleep(SLOW_CONNECTION_RETRY_DELAY);
}
}
}
if (restartConnection) {
restartConnection = false;
// reopen connection
logger.debug("Reconnecting...");
try {
connected = false;
connectSocket();
logger.debug("Test connection to {}:{}", ip, port);
sendCommand(new EiscpMessage.MessageBuilder().command(EiscpCommand.POWER_QUERY.getCommand())
.value(EiscpCommand.POWER_QUERY.getValue()).build());
} catch (Exception ex) {
logger.debug("Reconnection invoking error: {}", ex.getMessage());
sendConnectionErrorEvent(ex.getMessage());
}
}
}
logger.debug("Data listener stopped");
}
private void mysleep(long milli) {
try {
sleep(milli);
} catch (InterruptedException e) {
interrupted = true;
}
}
}
private class ConnectionSupervisor {
private Timer timer;
public ConnectionSupervisor(int milliseconds) {
logger.debug("Connection supervisor started, interval {} milliseconds", milliseconds);
timer = new Timer();
timer.schedule(new Task(), milliseconds, milliseconds);
}
public void stopConnectionTester() {
timer.cancel();
}
class Task extends TimerTask {
@Override
public void run() {
logger.debug("Test connection to {}:{}", ip, port);
sendCommand(new EiscpMessage.MessageBuilder().command(EiscpCommand.POWER_QUERY.getCommand())
.value(EiscpCommand.POWER_QUERY.getValue()).build());
}
}
}
private void sendConnectionErrorEvent(String errorMsg) {
// send message to event listeners
try {
for (OnkyoEventListener listener : listeners) {
listener.connectionError(ip, errorMsg);
}
} catch (Exception ex) {
logger.debug("Event listener invoking error: {}", ex.getMessage());
}
}
private void sendMessageEvent(EiscpMessage message) {
// send message to event listeners
try {
for (OnkyoEventListener listener : listeners) {
listener.statusUpdateReceived(ip, message);
}
} catch (Exception e) {
logger.debug("Event listener invoking error: {}", e.getMessage());
}
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
/**
* This interface defines interface to receive status updates from Onkyo receiver.
*
* @author Pauli Anttila - Initial contribution
*/
public interface OnkyoEventListener {
/**
* Procedure for receive status update from Onkyo AV receiver.
*
* @param data
* Received data.
*/
void statusUpdateReceived(String ip, EiscpMessage data);
/**
* Procedure for connection error events from Onkyo AV receiver.
*
* @param errorMsg
* Reason for error.
*/
void connectionError(String ip, String errorMsg);
}

View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import static org.openhab.binding.onkyo.internal.OnkyoBindingConstants.SUPPORTED_THING_TYPES_UIDS;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OnkyoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Paul Frank - Initial contribution
* @author Stewart Cossey - added dynamic state descriptor provider functions
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.onkyo")
public class OnkyoHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(OnkyoHandlerFactory.class);
private final Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();
private UpnpIOService upnpIOService;
private AudioHTTPServer audioHTTPServer;
private NetworkAddressService networkAddressService;
private OnkyoStateDescriptionProvider stateDescriptionProvider;
// url (scheme+server+port) to use for playing notification sounds
private String callbackUrl;
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
Dictionary<String, Object> properties = componentContext.getProperties();
callbackUrl = (String) properties.get("callbackUrl");
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
String callbackUrl = createCallbackUrl();
OnkyoHandler handler = new OnkyoHandler(thing, upnpIOService, audioHTTPServer, callbackUrl,
stateDescriptionProvider);
if (callbackUrl != null) {
@SuppressWarnings("unchecked")
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), handler, new Hashtable<>());
audioSinkRegistrations.put(thing.getUID().toString(), reg);
}
return handler;
}
return null;
}
private String createCallbackUrl() {
if (callbackUrl != null) {
return callbackUrl;
} else {
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.warn("No network interface could be found.");
return null;
}
// we do not use SSL as it can cause certificate validation issues.
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (port == -1) {
logger.warn("Cannot find port of the http service.");
return null;
}
return "http://" + ipAddress + ":" + port;
}
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ServiceRegistration<AudioSink> reg = audioSinkRegistrations.get(thing.getUID().toString());
if (reg != null) {
reg.unregister();
}
}
@Reference
protected void setUpnpIOService(UpnpIOService upnpIOService) {
this.upnpIOService = upnpIOService;
}
protected void unsetUpnpIOService(UpnpIOService upnpIOService) {
this.upnpIOService = null;
}
@Reference
protected void setAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = audioHTTPServer;
}
protected void unsetAudioHTTPServer(AudioHTTPServer audioHTTPServer) {
this.audioHTTPServer = null;
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
@Reference
protected void setDynamicStateDescriptionProvider(OnkyoStateDescriptionProvider provider) {
this.stateDescriptionProvider = provider;
}
protected void unsetDynamicStateDescriptionProvider(OnkyoStateDescriptionProvider provider) {
this.stateDescriptionProvider = null;
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Enumerates all supported Onkyo models.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public enum OnkyoModel {
// Please also add new supported models to README.md
HT_RC560("HT-RC560"),
TX_NR414("TX-NR414"),
TX_NR474("TX-NR474"),
TX_NR509("TX-NR509"),
TX_NR515("TX-NR515"),
TX_NR525("TX-NR525"),
TX_NR535("TX-NR535"),
TX_NR545("TX-NR545"),
TX_NR555("TX-NR555"),
TX_NR575("TX-NR575"),
TX_NR575E("TX-NR575E"),
TX_NR616("TX-NR616"),
TX_NR626("TX-NR626"),
TX_NR636("TX-NR636"),
TX_NR646("TX-NR646"),
TX_NR656("TX-NR656"),
TX_NR676("TX-NR676"),
TX_NR686("TX-NR686"),
TX_NR708("TX-NR708"),
TX_NR717("TX-NR717"),
TX_NR727("TX-NR727"),
TX_NR737("TX-NR737"),
TX_NR747("TX-NR747"),
TX_NR757("TX-NR757"),
TX_NR809("TX-NR809"),
TX_NR818("TX-NR818"),
TX_NR828("TX-NR828"),
TX_NR838("TX-NR838"),
TX_NR3007("TX-NR3007"),
TX_RZ900("TX-RZ900");
private final String id;
private OnkyoModel(String id) {
this.id = id;
}
public String getId() {
return id;
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.StateOption;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
/**
* The {@link OnkyoStateDescriptionProvider} class is a dynamic provider of state options while leaving other state
* description fields as original.
*
* @author Gregory Moyer - Initial contribution
* @author Stewart Cossey - Adapted for Onkyo Binding
*/
@Component(service = { DynamicStateDescriptionProvider.class, OnkyoStateDescriptionProvider.class })
@NonNullByDefault
public class OnkyoStateDescriptionProvider implements DynamicStateDescriptionProvider {
private final Map<ChannelUID, @Nullable List<StateOption>> channelOptionsMap = new ConcurrentHashMap<>();
public void setStateOptions(ChannelUID channelUID, List<StateOption> options) {
channelOptionsMap.put(channelUID, options);
}
@Override
public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original,
@Nullable Locale locale) {
List<StateOption> options = channelOptionsMap.get(channel.getUID());
if (options == null) {
return null;
}
StateDescriptionFragmentBuilder builder = (original == null) ? StateDescriptionFragmentBuilder.create()
: StateDescriptionFragmentBuilder.create(original);
return builder.withOptions(options).build().toStateDescription();
}
@Deactivate
public void deactivate() {
channelOptionsMap.clear();
}
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal;
/**
* List of Onkyo Net service types
*
* @author Marcel Verpaalen - Initial contribution
*/
public enum ServiceType {
MUSIC_SERVER(0x00),
FAVORITE(0x01),
VTUNER(0x02),
SIRIUSXM(0x03),
PANDORA(0x04),
RHAPSODY(0x06),
LASTFM(0x06),
NAPSTER(0x07),
SLACKER(0x08),
MEDIAFLY(0x09),
SPOTIFY(0x0A),
AUPEO(0x0B),
RADIKO(0x0C),
EONKYO(0x0D),
TUNEIN(0x0E),
MP3TUNES(0x0F),
SIMFY(0x10),
HOMEMEDIA(0x11),
DEEZER(0x12),
IHEARTRADIO(0x13),
AIRPLAY(0x18),
TIDAL(0x19),
ONKYO_MUSIC(0x1A),
USB(0xF0),
USB_REAR(0xF1),
INTERNETRADIO(0xF2),
NET(0xF3),
NONE(0xFF);
private final int id;
ServiceType(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static ServiceType getType(int value) {
for (ServiceType st : ServiceType.values()) {
if (st.getId() == value) {
return st;
}
}
return NONE;
}
@Override
public String toString() {
switch (this) {
case MUSIC_SERVER:
return "Music Server (DLNA)";
case FAVORITE:
return "Favorite";
case VTUNER:
return "vTuner";
case SIRIUSXM:
return "SiriusXM";
case PANDORA:
return "Pandora";
case RHAPSODY:
return "Rhapsody";
case LASTFM:
return "Last.fm";
case NAPSTER:
return "Napster";
case SLACKER:
return "Slacker";
case MEDIAFLY:
return "Mediafly";
case SPOTIFY:
return "Spotify";
case AUPEO:
return "AUPEO!";
case RADIKO:
return "radiko";
case EONKYO:
return "e-onkyo";
case TUNEIN:
return "TuneIn Radio";
case MP3TUNES:
return "MP3tunes";
case SIMFY:
return "Simfy";
case HOMEMEDIA:
return "Home Media";
case DEEZER:
return "Deezer";
case IHEARTRADIO:
return "iHeartRadio";
case AIRPLAY:
return "Airplay";
case TIDAL:
return "TIDAL";
case ONKYO_MUSIC:
return "onkyo music";
case USB:
return "USB/USB(Front)";
case USB_REAR:
return "USB(Rear)";
case INTERNETRADIO:
return "Internet Radio";
case NET:
return "NET";
case NONE:
return "None";
default:
return "Invalid/unknown";
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.automation.modules;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link OnkyoThingActions} defines the interface for all thing actions supported by the binding.
*
* @author Laurent Garnier - initial contribution
*/
@NonNullByDefault
public interface OnkyoThingActions {
public void sendRawCommand(@Nullable String command, @Nullable String value);
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.automation.modules;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.onkyo.internal.handler.OnkyoHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Some automation actions to be used with a {@link OnkyoThingActionsService}
* <p>
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
* the test <i>actions instanceof OnkyoThingActionsService</i> fails. This test can fail
* due to an issue in openHAB core v2.5.0 where the {@link OnkyoThingActionsService} class
* can be loaded by a different classloader than the <i>actions</i> instance.
*
* @author David Masshardt - initial contribution
*
*/
@ThingActionsScope(name = "onkyo")
@NonNullByDefault
public class OnkyoThingActionsService implements ThingActions, OnkyoThingActions {
private final Logger logger = LoggerFactory.getLogger(OnkyoThingActionsService.class);
private @Nullable OnkyoHandler handler;
@Override
@SuppressWarnings("null")
@RuleAction(label = "Onkyo sendRawCommand", description = "Action that sends raw command to the receiver")
public void sendRawCommand(@ActionInput(name = "command") @Nullable String command,
@ActionInput(name = "command") @Nullable String value) {
logger.debug("sendRawCommand called with raw command: {} value: {}", command, value);
if (handler == null) {
logger.warn("Onkyo Action service ThingHandler is null!");
return;
}
handler.sendRawCommand(command, value);
}
public static void sendRawCommand(@Nullable ThingActions actions, @Nullable String command,
@Nullable String value) {
invokeMethodOf(actions).sendRawCommand(command, value);
}
private static OnkyoThingActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(OnkyoThingActionsService.class.getName())) {
if (actions instanceof OnkyoThingActions) {
return (OnkyoThingActions) actions;
} else {
return (OnkyoThingActions) Proxy.newProxyInstance(OnkyoThingActions.class.getClassLoader(),
new Class[] { OnkyoThingActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of OnkyoThingActionsService");
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof OnkyoHandler) {
this.handler = (OnkyoHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return this.handler;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.config;
/**
* Configuration class for {@link OnkyoBinding} device.
*
* @author Pauli Anttila - Initial contribution
*/
public class OnkyoDeviceConfiguration {
public String ipAddress;
public int port;
public String udn;
public int refreshInterval;
public int volumeLimit;
public double volumeScale = 1.0d;
@Override
public String toString() {
String str = "";
str += "ipAddress = " + ipAddress;
str += ", port = " + port;
str += ", udn = " + udn;
str += ", refreshInterval = " + refreshInterval;
str += ", volumeLimit = " + volumeLimit;
str += ", volumeScale = " + volumeScale;
return str;
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.discovery;
import static org.openhab.binding.onkyo.internal.OnkyoBindingConstants.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.binding.onkyo.internal.OnkyoModel;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An UpnpDiscoveryParticipant which allows to discover Onkyo AVRs.
*
* @author Paul Frank - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true)
public class OnkyoUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(OnkyoUpnpDiscoveryParticipant.class);
private boolean isAutoDiscoveryEnabled;
private Set<ThingTypeUID> supportedThingTypes;
public OnkyoUpnpDiscoveryParticipant() {
this.isAutoDiscoveryEnabled = true;
this.supportedThingTypes = SUPPORTED_THING_TYPES_UIDS;
}
/**
* Called at the service activation.
*
* @param componentContext
*/
@Activate
protected void activate(ComponentContext componentContext) {
if (componentContext.getProperties() != null) {
String autoDiscoveryPropertyValue = (String) componentContext.getProperties().get("enableAutoDiscovery");
if (StringUtils.isNotEmpty(autoDiscoveryPropertyValue)) {
isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
}
}
supportedThingTypes = isAutoDiscoveryEnabled ? SUPPORTED_THING_TYPES_UIDS : new HashSet<>();
}
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return supportedThingTypes;
}
@Override
public @Nullable 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<String, Object> properties = new HashMap<>(2, 1);
properties.put(HOST_PARAMETER, device.getIdentity().getDescriptorURL().getHost());
properties.put(UDN_PARAMETER, device.getIdentity().getUdn().getIdentifierString());
result = DiscoveryResultBuilder.create(thingUid).withLabel(label).withProperties(properties).build();
}
return result;
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
ThingUID result = null;
if (isAutoDiscoveryEnabled) {
if (StringUtils.containsIgnoreCase(device.getDetails().getManufacturerDetails().getManufacturer(),
MANUFACTURER)) {
logger.debug("Manufacturer matched: search: {}, device value: {}.", MANUFACTURER,
device.getDetails().getManufacturerDetails().getManufacturer());
if (StringUtils.containsIgnoreCase(device.getType().getType(), UPNP_DEVICE_TYPE)) {
logger.debug("Device type matched: search: {}, device value: {}.", UPNP_DEVICE_TYPE,
device.getType().getType());
String deviceModel = device.getDetails().getModelDetails() != null
? device.getDetails().getModelDetails().getModelName()
: null;
logger.debug("Device model: {}.", deviceModel);
ThingTypeUID thingTypeUID = findThingType(deviceModel);
result = new ThingUID(thingTypeUID, device.getIdentity().getUdn().getIdentifierString());
}
}
}
return result;
}
private ThingTypeUID findThingType(@Nullable String deviceModel) {
ThingTypeUID thingTypeUID = THING_TYPE_ONKYO_UNSUPPORTED;
for (ThingTypeUID thingType : SUPPORTED_THING_TYPES_UIDS) {
if (thingType.getId().equalsIgnoreCase(deviceModel)) {
return thingType;
}
}
if (isSupportedDeviceModel(deviceModel)) {
thingTypeUID = THING_TYPE_ONKYOAV;
}
return thingTypeUID;
}
/**
* Return true only if the given device model is supported.
*
* @param deviceModel
* @return
*/
private boolean isSupportedDeviceModel(final @Nullable String deviceModel) {
return StringUtils.isNotBlank(deviceModel) && Arrays.stream(OnkyoModel.values())
.anyMatch(model -> StringUtils.startsWithIgnoreCase(deviceModel, model.getId()));
}
}

View File

@@ -0,0 +1,226 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.eiscp;
/**
* Represents all possible eISCP commands.
*
* @author Thomas.Eichstaedt-Engelen - initial contribution
* @author Pauli Anttila - add additional commands
* @author Paul Frank - update for openHAB 2
* @author Marcel Verpaalen - fix issues with some Zone 2 commands
*/
public enum EiscpCommand {
// Main zone
POWER_QUERY("PWR", "QSTN"),
POWER_SET("PWR", "%02X"),
POWER("PWR", ""),
MUTE_QUERY("AMT", "QSTN"),
MUTE_SET("AMT", "%02X"),
MUTE("AMT", ""),
VOLUME_UP("MVL", "UP"),
VOLUME_DOWN("MVL", "DOWN"),
VOLUME_QUERY("MVL", "QSTN"),
VOLUME_SET("MVL", "%02X"),
VOLUME("MVL", ""),
SOURCE_UP("SLI", "UP"),
SOURCE_DOWN("SLI", "DOWN"),
SOURCE_QUERY("SLI", "QSTN"),
SOURCE_SET("SLI", "%02X"),
SOURCE("SLI", ""),
LISTEN_MODE_UP("LMD", "UP"),
LISTEN_MODE_DOWN("LMD", "DOWN"),
LISTEN_MODE_QUERY("LMD", "QSTN"),
LISTEN_MODE_SET("LMD", "%02X"),
LISTEN_MODE("LMD", ""),
INFO_QUERY("NRI", "QSTN"),
INFO("NRI", ""),
NETUSB_OP_PLAY("NTC", "PLAY"),
NETUSB_OP_STOP("NTC", "STOP"),
NETUSB_OP_PAUSE("NTC", "PAUSE"),
NETUSB_OP_TRACKUP("NTC", "TRUP"),
NETUSB_OP_TRACKDWN("NTC", "TRDN"),
NETUSB_OP_FF("NTC", "FF"),
NETUSB_OP_REW("NTC", "REW"),
NETUSB_OP_REPEAT("NTC", "REPEAT"),
NETUSB_OP_RANDOM("NTC", "RANDOM"),
NETUSB_OP_DISPLAY("NTC", "DISPLAY"),
NETUSB_OP_RIGHT("NTC", "RIGHT"),
NETUSB_OP_LEFT("NTC", "LEFT"),
NETUSB_OP_UP("NTC", "UP"),
NETUSB_OP_DOWN("NTC", "DOWN"),
NETUSB_OP_SELECT("NTC", "SELECT"),
NETUSB_OP_1("NTC", "1"),
NETUSB_OP_2("NTC", "2"),
NETUSB_OP_3("NTC", "3"),
NETUSB_OP_4("NTC", "4"),
NETUSB_OP_5("NTC", "5"),
NETUSB_OP_6("NTC", "6"),
NETUSB_OP_7("NTC", "7"),
NETUSB_OP_8("NTC", "8"),
NETUSB_OP_9("NTC", "9"),
NETUSB_OP_0("NTC", "0"),
NETUSB_OP_DELETE("NTC", "DELETE"),
NETUSB_OP_CAPS("NTC", "CAPS"),
NETUSB_OP_SETUP("NTC", "SETUP"),
NETUSB_OP_RETURN("NTC", "RETURN"),
NETUSB_OP_CHANUP("NTC", "CHUP"),
NETUSB_OP_CHANDWN("NTC", "CHDN"),
NETUSB_OP_MENU("NTC", "MENU"),
NETUSB_OP_TOPMENU("NTC", "TOP"),
NETUSB_SONG_ARTIST_QUERY("NAT", "QSTN"),
NETUSB_SONG_ARTIST("NAT", ""),
NETUSB_SONG_ALBUM_QUERY("NAL", "QSTN"),
NETUSB_SONG_ALBUM("NAL", ""),
NETUSB_SONG_TITLE_QUERY("NTI", "QSTN"),
NETUSB_SONG_TITLE("NTI", ""),
NETUSB_SONG_ELAPSEDTIME_QUERY("NTM", "QSTN"),
NETUSB_SONG_ELAPSEDTIME("NTM", ""),
NETUSB_SONG_TRACK_QUERY("NTR", "QSTN"),
NETUSB_SONG_TRACK("NTR", ""),
NETUSB_PLAY_STATUS_QUERY("NST", "QSTN"),
NETUSB_PLAY_STATUS("NST", ""),
NETUSB_MENU_SELECT("NLS", "L%X"),
NETUSB_MENU("NLS", ""),
NETUSB_TITLE("NLT", ""),
NETUSB_TITLE_QUERY("NLT", "QSTN"),
NETUSB_ALBUM_ART_QUERY("NJA", "REQ"),
NETUSB_ALBUM_ART("NJA", ""),
/*
* Zone 2
*/
ZONE2_POWER_QUERY("ZPW", "QSTN"),
ZONE2_POWER_SET("ZPW", "%02X"),
ZONE2_POWER("ZPW", ""),
ZONE2_MUTE_QUERY("ZMT", "QSTN"),
ZONE2_MUTE_SET("ZMT", "%02X"),
ZONE2_MUTE("ZMT", ""),
ZONE2_VOLUME_UP("ZVL", "UP"),
ZONE2_VOLUME_DOWN("ZVL", "DOWN"),
ZONE2_VOLUME_QUERY("ZVL", "QSTN"),
ZONE2_VOLUME_SET("ZVL", "%02X"),
ZONE2_VOLUME("ZVL", ""),
ZONE2_SOURCE_UP("SLZ", "UP"),
ZONE2_SOURCE_DOWN("SLZ", "DOWN"),
ZONE2_SOURCE_QUERY("SLZ", "QSTN"),
ZONE2_SOURCE_SET("SLZ", "%02X"),
ZONE2_SOURCE("SLZ", ""),
/*
* Zone 3
*/
ZONE3_POWER_QUERY("PW3", "QSTN"),
ZONE3_POWER_SET("PW3", "%02X"),
ZONE3_POWER("PW3", ""),
ZONE3_MUTE_QUERY("MT3", "QSTN"),
ZONE3_MUTE_SET("MT3", "%02X"),
ZONE3_MUTE("MT3", ""),
ZONE3_VOLUME_UP("VL3", "UP"),
ZONE3_VOLUME_DOWN("VL3", "DOWN"),
ZONE3_VOLUME_QUERY("VL3", "QSTN"),
ZONE3_VOLUME_SET("VL3", "%02X"),
ZONE3_VOLUME("VL3", ""),
ZONE3_SOURCE_UP("SL3", "UP"),
ZONE3_SOURCE_DOWN("SL3", "DOWN"),
ZONE3_SOURCE_QUERY("SL3", "QSTN"),
ZONE3_SOURCE_SET("SL3", "%02X"),
ZONE3_SOURCE("SL3", "");
public static enum Zone {
MAIN,
ZONE1,
ZONE2,
ZONE3
}
private String command;
private String value;
private EiscpCommand(String command, String value) {
this.command = command;
this.value = value;
}
/**
* @return the iscp command string (example 'PWR')
*/
public String getCommand() {
return command;
}
/**
* @return the iscp value string (example 'QSTN')
*/
public String getValue() {
return value;
}
public static EiscpCommand getCommandForZone(Zone zone, EiscpCommand baseCommand) throws IllegalArgumentException {
if (zone == Zone.MAIN || zone == Zone.ZONE1) {
return baseCommand;
} else {
return EiscpCommand.valueOf(zone.toString() + "_" + baseCommand);
}
}
/**
* @param command the command to find a matching command name for.
* @return the commandName that is associated with the passed command.
*/
public static EiscpCommand getCommandByCommandStr(String command) throws IllegalArgumentException {
for (EiscpCommand candidate : values()) {
if (candidate.getCommand().equals(command)) {
return candidate;
}
}
throw new IllegalArgumentException("There is no matching commandName for command '" + command + "'");
}
/**
* @param command the command to find a matching command name for.
* @param value the value to find a matching value for.
*
* @return the commandName that is associated with the passed command.
*/
public static EiscpCommand getCommandByCommandAndValueStr(String command, String value)
throws IllegalArgumentException {
for (EiscpCommand candidate : values()) {
if (candidate.getCommand().equals(command) && candidate.getValue().equals(value)) {
return candidate;
}
}
throw new IllegalArgumentException(
"There is no matching commandName for command '" + command + "' and value '" + value + "'");
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.eiscp;
/**
* Exception for eISCP errors.
*
* @author Pauli Anttila - Initial contribution
*/
public class EiscpException extends Exception {
private static final long serialVersionUID = -7970958467980752003L;
public EiscpException() {
super();
}
public EiscpException(String message) {
super(message);
}
public EiscpException(String message, Throwable cause) {
super(message, cause);
}
public EiscpException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.eiscp;
/**
* Class to handle Onkyo eISCP messages.
*
* @author Pauli Anttila - Initial contribution
*/
public class EiscpMessage {
private String command = "";
private String value = "";
private EiscpMessage(MessageBuilder messageBuilder) {
this.command = messageBuilder.command;
this.value = messageBuilder.value;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
String str = "[";
str += "command=" + command;
str += ", value=" + value;
str += "]";
return str;
}
public static class MessageBuilder {
private String command;
private String value;
public MessageBuilder command(String command) {
this.command = command;
return this;
}
public MessageBuilder value(String value) {
this.value = value;
return this;
}
public EiscpMessage build() {
return new EiscpMessage(this);
}
}
}

View File

@@ -0,0 +1,270 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.eiscp;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.Arrays;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to handle Onkyo eISCP protocol.
*
* @author Pauli Anttila - Initial contribution
*/
public class EiscpProtocol {
private static final Logger LOGGER = LoggerFactory.getLogger(EiscpProtocol.class);
/**
* Wraps a command in a eISCP data message (data characters).
*
* @param msg
* eISCP command.
* @return String holding the full eISCP message packet
*/
public static String createEiscpPdu(EiscpMessage msg) {
String data = msg.getCommand() + msg.getValue();
StringBuilder sb = new StringBuilder();
int eiscpDataSize = 2 + data.length() + 1; // this is the eISCP data size
/*
* This is where I construct the entire message character by character.
* Each char is represented by a 2 digit hex value
*/
sb.append("ISCP");
// the following are all in HEX representing one char
// 4 char Big Endian Header
sb.append((char) 0x00);
sb.append((char) 0x00);
sb.append((char) 0x00);
sb.append((char) 0x10);
// 4 char Big Endian data size
sb.append((char) ((eiscpDataSize >> 24) & 0xFF));
sb.append((char) ((eiscpDataSize >> 16) & 0xFF));
sb.append((char) ((eiscpDataSize >> 8) & 0xFF));
sb.append((char) (eiscpDataSize & 0xFF));
// eISCP version = "01";
sb.append((char) 0x01);
// 3 chars reserved = "00"+"00"+"00";
sb.append((char) 0x00);
sb.append((char) 0x00);
sb.append((char) 0x00);
// eISCP data
// Start Character
sb.append("!");
// eISCP data - unit type char '1' is receiver
sb.append("1");
// eISCP data - 3 char command and param ie PWR01
sb.append(data);
// msg end - EOF
sb.append((char) 0x0D);
if (LOGGER.isTraceEnabled()) {
String d = sb.toString();
LOGGER.trace("Created eISCP message: {} -> {}", HexUtils.bytesToHex(d.getBytes()), toPrintable(d));
}
return sb.toString();
}
/**
* Method to read eISCP message from input stream.
*
* @return message
*
* @throws IOException
* @throws InterruptedException
* @throws EiscpException
*/
public static EiscpMessage getNextMessage(DataInputStream stream)
throws IOException, InterruptedException, EiscpException {
while (true) {
// 1st 4 chars are the lead in
byte firstByte = stream.readByte();
if (firstByte != 'I') {
LOGGER.trace("Expected character 'I', received '{}'",
toPrintable(new String(new byte[] { firstByte })));
continue;
}
if (stream.readByte() != 'S') {
continue;
}
if (stream.readByte() != 'C') {
continue;
}
if (stream.readByte() != 'P') {
continue;
}
// header size
final int headerSize = (stream.readByte() & 0xFF) << 24 | (stream.readByte() & 0xFF) << 16
| (stream.readByte() & 0xFF) << 8 | (stream.readByte() & 0xFF);
if (headerSize != 16) {
throw new EiscpException("Unsupported header size: " + headerSize);
}
// data size
final int dataSize = (stream.readByte() & 0xFF) << 24 | (stream.readByte() & 0xFF) << 16
| (stream.readByte() & 0xFF) << 8 | (stream.readByte() & 0xFF);
LOGGER.trace("Data size: {}", dataSize);
// version
final byte versionChar = stream.readByte();
if (versionChar != 1) {
throw new EiscpException("Unsupported version " + String.valueOf(versionChar));
}
// skip 3 reserved bytes
byte b1 = stream.readByte();
byte b2 = stream.readByte();
byte b3 = stream.readByte();
byte[] data = new byte[dataSize];
int bytesReceived = 0;
try {
while (bytesReceived < dataSize) {
bytesReceived = bytesReceived + stream.read(data, bytesReceived, data.length - bytesReceived);
if (LOGGER.isTraceEnabled()) {
// create header for debugging purposes
final StringBuilder sb = new StringBuilder();
sb.append("ISCP");
sb.append((char) 0x00);
sb.append((char) 0x00);
sb.append((char) 0x00);
sb.append((char) 0x10);
// 4 char Big Endian data size
sb.append((char) ((dataSize >> 24) & 0xFF));
sb.append((char) ((dataSize >> 16) & 0xFF));
sb.append((char) ((dataSize >> 8) & 0xFF));
sb.append((char) (dataSize & 0xFF));
// eiscp version;
sb.append((char) versionChar);
// reserved bytes
sb.append((char) b1).append((char) b2).append((char) b3);
// data
sb.append(new String(data, "UTF-8"));
LOGGER.trace("Received eISCP message, {} -> {}", HexUtils.bytesToHex(sb.toString().getBytes()),
toPrintable(sb.toString()));
}
}
} catch (IOException t) {
if (bytesReceived != dataSize) {
LOGGER.debug("Received bad data: '{}'", toPrintable(new String(data, "UTF-8")));
throw new EiscpException(
"Data missing, expected + " + dataSize + " received " + bytesReceived + " bytes");
} else {
throw t;
}
}
// start char
final byte startChar = data[0];
if (startChar != '!') {
throw new EiscpException("Illegal start char " + startChar);
}
// unit type
@SuppressWarnings("unused")
final byte unitType = data[1];
// data should be end to "[EOF]" or "[EOF][CR]" or "[EOF][CR][LF]" characters depend on model
// [EOF] End of File ASCII Code 0x1A
// [CR] Carriage Return ASCII Code 0x0D (\r)
// [LF] Line Feed ASCII Code 0x0A (\n)
int endBytes = 0;
// TODO: Simplify this by implementation, which find [EOF] character and ignore rest of the bytes after
// that. But before that, proper junit test should be implement to be sure that it does not broke
// anything.
if (data[dataSize - 5] == (byte) 0x1A && data[dataSize - 4] == '\n' && data[dataSize - 3] == '\n'
&& data[dataSize - 2] == '\r' && data[dataSize - 1] == '\n') {
// skip "[EOF][LF][LF][CR][LF]"
endBytes = 5;
} else if (data[dataSize - 4] == (byte) 0x1A && data[dataSize - 3] == '\r' && data[dataSize - 2] == '\n'
&& data[dataSize - 1] == 0x00) {
// skip "[EOF][CR][LF][NULL]"
endBytes = 4;
} else if (data[dataSize - 3] == (byte) 0x1A && data[dataSize - 2] == '\r' && data[dataSize - 1] == '\n') {
// skip "[EOF][CR][LF]"
endBytes = 3;
} else if (data[dataSize - 2] == (byte) 0x1A && data[dataSize - 1] == '\r') {
// "[EOF][CR]"
endBytes = 2;
} else if (data[dataSize - 1] == (byte) 0x1A) {
// "[EOF]"
endBytes = 1;
} else {
throw new EiscpException("Illegal end of message");
}
try {
String command = new String(Arrays.copyOfRange(data, 2, 5));
String value = new String(Arrays.copyOfRange(data, 5, data.length - endBytes));
return new EiscpMessage.MessageBuilder().command(command).value(value).build();
} catch (Exception e) {
throw new EiscpException("Fatal error occurred when parsing eISCP message, cause=" + e.getCause());
}
}
}
public static String toPrintable(final String rawData) {
final StringBuilder sb = new StringBuilder();
if (rawData == null) {
return "";
}
for (final char c : rawData.toCharArray()) {
if (c <= 31 || c == 127) {
switch (c) {
case '\r':
sb.append("[CR]");
break;
case '\n':
sb.append("[LF]");
break;
case (byte) 0x1A:
sb.append("[EOF]");
break;
default:
sb.append(String.format("[%02X]", (int) c));
}
} else {
sb.append(c);
}
}
return sb.toString();
}
}

View File

@@ -0,0 +1,915 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.handler;
import static org.openhab.binding.onkyo.internal.OnkyoBindingConstants.*;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.openhab.binding.onkyo.internal.OnkyoAlbumArt;
import org.openhab.binding.onkyo.internal.OnkyoConnection;
import org.openhab.binding.onkyo.internal.OnkyoEventListener;
import org.openhab.binding.onkyo.internal.OnkyoStateDescriptionProvider;
import org.openhab.binding.onkyo.internal.ServiceType;
import org.openhab.binding.onkyo.internal.automation.modules.OnkyoThingActionsService;
import org.openhab.binding.onkyo.internal.config.OnkyoDeviceConfiguration;
import org.openhab.binding.onkyo.internal.eiscp.EiscpCommand;
import org.openhab.binding.onkyo.internal.eiscp.EiscpMessage;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.NextPreviousType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.RewindFastforwardType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* The {@link OnkyoHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Paul Frank - Initial contribution
* @author Marcel Verpaalen - parsing additional commands
* @author Pauli Anttila - lot of refactoring
* @author Stewart Cossey - add dynamic state description provider
*/
public class OnkyoHandler extends UpnpAudioSinkHandler implements OnkyoEventListener {
private final Logger logger = LoggerFactory.getLogger(OnkyoHandler.class);
private OnkyoDeviceConfiguration configuration;
private OnkyoConnection connection;
private ScheduledFuture<?> resourceUpdaterFuture;
@SuppressWarnings("unused")
private int currentInput = -1;
private State volumeLevelZone1 = UnDefType.UNDEF;
private State volumeLevelZone2 = UnDefType.UNDEF;
private State volumeLevelZone3 = UnDefType.UNDEF;
private State lastPowerState = OnOffType.OFF;
private final OnkyoStateDescriptionProvider stateDescriptionProvider;
private final OnkyoAlbumArt onkyoAlbumArt = new OnkyoAlbumArt();
private static final int NET_USB_ID = 43;
public OnkyoHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer, String callbackUrl,
OnkyoStateDescriptionProvider stateDescriptionProvider) {
super(thing, upnpIOService, audioHTTPServer, callbackUrl);
this.stateDescriptionProvider = stateDescriptionProvider;
}
/**
* Initialize the state of the receiver.
*/
@Override
public void initialize() {
logger.debug("Initializing handler for Onkyo Receiver");
configuration = getConfigAs(OnkyoDeviceConfiguration.class);
logger.info("Using configuration: {}", configuration.toString());
connection = new OnkyoConnection(configuration.ipAddress, configuration.port);
connection.addEventListener(this);
scheduler.execute(() -> {
logger.debug("Open connection to Onkyo Receiver @{}", connection.getConnectionName());
connection.openConnection();
if (connection.isConnected()) {
updateStatus(ThingStatus.ONLINE);
sendCommand(EiscpCommand.INFO_QUERY);
}
});
if (configuration.refreshInterval > 0) {
// Start resource refresh updater
resourceUpdaterFuture = scheduler.scheduleWithFixedDelay(() -> {
try {
logger.debug("Send resource update requests to Onkyo Receiver @{}", connection.getConnectionName());
checkStatus();
} catch (LinkageError e) {
logger.warn("Failed to send resource update requests to Onkyo Receiver @{}. Cause: {}",
connection.getConnectionName(), e.getMessage());
} catch (Exception ex) {
logger.warn("Exception in resource refresh Thread Onkyo Receiver @{}. Cause: {}",
connection.getConnectionName(), ex.getMessage());
}
}, configuration.refreshInterval, configuration.refreshInterval, TimeUnit.SECONDS);
}
}
@Override
public void dispose() {
super.dispose();
if (resourceUpdaterFuture != null) {
resourceUpdaterFuture.cancel(true);
}
if (connection != null) {
connection.removeEventListener(this);
connection.closeConnection();
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand for channel {}: {}", channelUID.getId(), command.toString());
switch (channelUID.getId()) {
/*
* ZONE 1
*/
case CHANNEL_POWER:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.POWER_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.POWER_QUERY);
}
break;
case CHANNEL_MUTE:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.MUTE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.MUTE_QUERY);
}
break;
case CHANNEL_VOLUME:
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, command);
break;
case CHANNEL_INPUT:
if (command instanceof DecimalType) {
selectInput(((DecimalType) command).intValue());
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.SOURCE_QUERY);
}
break;
case CHANNEL_LISTENMODE:
if (command instanceof DecimalType) {
sendCommand(EiscpCommand.LISTEN_MODE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.LISTEN_MODE_QUERY);
}
break;
/*
* ZONE 2
*/
case CHANNEL_POWERZONE2:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.ZONE2_POWER_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE2_POWER_QUERY);
}
break;
case CHANNEL_MUTEZONE2:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.ZONE2_MUTE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE2_MUTE_QUERY);
}
break;
case CHANNEL_VOLUMEZONE2:
handleVolumeSet(EiscpCommand.Zone.ZONE2, volumeLevelZone2, command);
break;
case CHANNEL_INPUTZONE2:
if (command instanceof DecimalType) {
sendCommand(EiscpCommand.ZONE2_SOURCE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE2_SOURCE_QUERY);
}
break;
/*
* ZONE 3
*/
case CHANNEL_POWERZONE3:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.ZONE3_POWER_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE3_POWER_QUERY);
}
break;
case CHANNEL_MUTEZONE3:
if (command instanceof OnOffType) {
sendCommand(EiscpCommand.ZONE3_MUTE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE3_MUTE_QUERY);
}
break;
case CHANNEL_VOLUMEZONE3:
handleVolumeSet(EiscpCommand.Zone.ZONE3, volumeLevelZone3, command);
break;
case CHANNEL_INPUTZONE3:
if (command instanceof DecimalType) {
sendCommand(EiscpCommand.ZONE3_SOURCE_SET, command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.ZONE3_SOURCE_QUERY);
}
break;
/*
* NET PLAYER
*/
case CHANNEL_CONTROL:
if (command instanceof PlayPauseType) {
if (command.equals(PlayPauseType.PLAY)) {
sendCommand(EiscpCommand.NETUSB_OP_PLAY);
} else if (command.equals(PlayPauseType.PAUSE)) {
sendCommand(EiscpCommand.NETUSB_OP_PAUSE);
}
} else if (command instanceof NextPreviousType) {
if (command.equals(NextPreviousType.NEXT)) {
sendCommand(EiscpCommand.NETUSB_OP_TRACKUP);
} else if (command.equals(NextPreviousType.PREVIOUS)) {
sendCommand(EiscpCommand.NETUSB_OP_TRACKDWN);
}
} else if (command instanceof RewindFastforwardType) {
if (command.equals(RewindFastforwardType.REWIND)) {
sendCommand(EiscpCommand.NETUSB_OP_REW);
} else if (command.equals(RewindFastforwardType.FASTFORWARD)) {
sendCommand(EiscpCommand.NETUSB_OP_FF);
}
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_PLAY_STATUS_QUERY);
}
break;
case CHANNEL_PLAY_URI:
handlePlayUri(command);
break;
case CHANNEL_ALBUM_ART:
case CHANNEL_ALBUM_ART_URL:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_ALBUM_ART_QUERY);
}
break;
case CHANNEL_ARTIST:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_SONG_ARTIST_QUERY);
}
break;
case CHANNEL_ALBUM:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_SONG_ALBUM_QUERY);
}
break;
case CHANNEL_TITLE:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_SONG_TITLE_QUERY);
}
break;
case CHANNEL_CURRENTPLAYINGTIME:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_SONG_ELAPSEDTIME_QUERY);
}
break;
/*
* NET MENU
*/
case CHANNEL_NET_MENU_CONTROL:
if (command instanceof StringType) {
final String cmdName = command.toString();
handleNetMenuCommand(cmdName);
}
break;
case CHANNEL_NET_MENU_TITLE:
if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.NETUSB_TITLE_QUERY);
}
break;
/*
* MISC
*/
default:
logger.debug("Command received for an unknown channel: {}", channelUID.getId());
break;
}
}
private void populateInputs(NodeList selectorlist) {
List<StateOption> options = new ArrayList<>();
for (int i = 0; i < selectorlist.getLength(); i++) {
Element selectorItem = (Element) selectorlist.item(i);
options.add(new StateOption(String.valueOf(Integer.parseInt(selectorItem.getAttribute("id"), 16)),
selectorItem.getAttribute("name")));
}
logger.debug("Got Input List from Receiver {}", options);
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_INPUT), options);
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_INPUTZONE2), options);
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_INPUTZONE3), options);
}
private void doPowerOnCheck(State state) {
if (configuration.refreshInterval == 0 && lastPowerState == OnOffType.OFF && state == OnOffType.ON) {
sendCommand(EiscpCommand.INFO_QUERY);
}
lastPowerState = state;
}
@Override
public void statusUpdateReceived(String ip, EiscpMessage data) {
logger.debug("Received status update from Onkyo Receiver @{}: data={}", connection.getConnectionName(), data);
updateStatus(ThingStatus.ONLINE);
try {
EiscpCommand receivedCommand = null;
try {
receivedCommand = EiscpCommand.getCommandByCommandAndValueStr(data.getCommand(), "");
} catch (IllegalArgumentException ex) {
logger.debug("Received unknown status update from Onkyo Receiver @{}: data={}",
connection.getConnectionName(), data);
return;
}
logger.debug("Received command {}", receivedCommand);
switch (receivedCommand) {
/*
* ZONE 1
*/
case POWER:
State powerState = convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class);
updateState(CHANNEL_POWER, powerState);
doPowerOnCheck(powerState);
break;
case MUTE:
updateState(CHANNEL_MUTE, convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class));
break;
case VOLUME:
volumeLevelZone1 = handleReceivedVolume(
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
updateState(CHANNEL_VOLUME, volumeLevelZone1);
break;
case SOURCE:
updateState(CHANNEL_INPUT, convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
break;
case LISTEN_MODE:
updateState(CHANNEL_LISTENMODE,
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
break;
/*
* ZONE 2
*/
case ZONE2_POWER:
State powerZone2State = convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class);
updateState(CHANNEL_POWERZONE2, powerZone2State);
doPowerOnCheck(powerZone2State);
break;
case ZONE2_MUTE:
updateState(CHANNEL_MUTEZONE2, convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class));
break;
case ZONE2_VOLUME:
volumeLevelZone2 = handleReceivedVolume(
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
updateState(CHANNEL_VOLUMEZONE2, volumeLevelZone2);
break;
case ZONE2_SOURCE:
updateState(CHANNEL_INPUTZONE2,
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
break;
/*
* ZONE 3
*/
case ZONE3_POWER:
State powerZone3State = convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class);
updateState(CHANNEL_POWERZONE3, powerZone3State);
doPowerOnCheck(powerZone3State);
break;
case ZONE3_MUTE:
updateState(CHANNEL_MUTEZONE3, convertDeviceValueToOpenHabState(data.getValue(), OnOffType.class));
break;
case ZONE3_VOLUME:
volumeLevelZone3 = handleReceivedVolume(
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
updateState(CHANNEL_VOLUMEZONE3, volumeLevelZone3);
break;
case ZONE3_SOURCE:
updateState(CHANNEL_INPUTZONE3,
convertDeviceValueToOpenHabState(data.getValue(), DecimalType.class));
break;
/*
* NET PLAYER
*/
case NETUSB_SONG_ARTIST:
updateState(CHANNEL_ARTIST, convertDeviceValueToOpenHabState(data.getValue(), StringType.class));
break;
case NETUSB_SONG_ALBUM:
updateState(CHANNEL_ALBUM, convertDeviceValueToOpenHabState(data.getValue(), StringType.class));
break;
case NETUSB_SONG_TITLE:
updateState(CHANNEL_TITLE, convertDeviceValueToOpenHabState(data.getValue(), StringType.class));
break;
case NETUSB_SONG_ELAPSEDTIME:
updateState(CHANNEL_CURRENTPLAYINGTIME,
convertDeviceValueToOpenHabState(data.getValue(), StringType.class));
break;
case NETUSB_PLAY_STATUS:
updateState(CHANNEL_CONTROL, convertNetUsbPlayStatus(data.getValue()));
break;
case NETUSB_ALBUM_ART:
updateAlbumArt(data.getValue());
break;
case NETUSB_TITLE:
updateNetTitle(data.getValue());
break;
case NETUSB_MENU:
updateNetMenu(data.getValue());
break;
/*
* MISC
*/
case INFO:
processInfo(data.getValue());
logger.debug("Info message: '{}'", data.getValue());
break;
default:
logger.debug("Received unhandled status update from Onkyo Receiver @{}: data={}",
connection.getConnectionName(), data);
}
} catch (Exception ex) {
logger.warn("Exception in statusUpdateReceived for Onkyo Receiver @{}. Cause: {}, data received: {}",
connection.getConnectionName(), ex.getMessage(), data);
}
}
private void processInfo(String infoXML) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
try (StringReader sr = new StringReader(infoXML)) {
InputSource is = new InputSource(sr);
Document doc = builder.parse(is);
NodeList selectableInputs = doc.getDocumentElement().getElementsByTagName("selector");
populateInputs(selectableInputs);
}
} catch (ParserConfigurationException | SAXException | IOException e) {
logger.debug("Error occured during Info XML parsing.", e);
}
}
@Override
public void connectionError(String ip, String errorMsg) {
logger.debug("Connection error occurred to Onkyo Receiver @{}", ip);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMsg);
}
private State convertDeviceValueToOpenHabState(String data, Class<?> classToConvert) {
State state = UnDefType.UNDEF;
try {
int index;
if (data.contentEquals("N/A")) {
state = UnDefType.UNDEF;
} else if (classToConvert == OnOffType.class) {
index = Integer.parseInt(data, 16);
state = index == 0 ? OnOffType.OFF : OnOffType.ON;
} else if (classToConvert == DecimalType.class) {
index = Integer.parseInt(data, 16);
state = new DecimalType(index);
} else if (classToConvert == PercentType.class) {
index = Integer.parseInt(data, 16);
state = new PercentType(index);
} else if (classToConvert == StringType.class) {
state = new StringType(data);
}
} catch (Exception e) {
logger.debug("Cannot convert value '{}' to data type {}", data, classToConvert);
}
logger.debug("Converted data '{}' to openHAB state '{}' ({})", data, state, classToConvert);
return state;
}
private void handleNetMenuCommand(String cmdName) {
if ("Up".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_UP);
} else if ("Down".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_DOWN);
} else if ("Select".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_SELECT);
} else if ("PageUp".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_LEFT);
} else if ("PageDown".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_RIGHT);
} else if ("Back".equals(cmdName)) {
sendCommand(EiscpCommand.NETUSB_OP_RETURN);
} else if (cmdName.matches("Select[0-9]")) {
int pos = Integer.parseInt(cmdName.substring(6));
sendCommand(EiscpCommand.NETUSB_MENU_SELECT, new DecimalType(pos));
} else {
logger.debug("Received unknown menucommand {}", cmdName);
}
}
private void selectInput(int inputId) {
sendCommand(EiscpCommand.SOURCE_SET, new DecimalType(inputId));
currentInput = inputId;
}
@SuppressWarnings("unused")
private void onInputChanged(int newInput) {
currentInput = newInput;
if (newInput != NET_USB_ID) {
resetNetMenu();
updateState(CHANNEL_ARTIST, UnDefType.UNDEF);
updateState(CHANNEL_ALBUM, UnDefType.UNDEF);
updateState(CHANNEL_TITLE, UnDefType.UNDEF);
updateState(CHANNEL_CURRENTPLAYINGTIME, UnDefType.UNDEF);
}
}
private void updateAlbumArt(String data) {
onkyoAlbumArt.addFrame(data);
if (onkyoAlbumArt.isAlbumCoverReady()) {
try {
byte[] imgData = onkyoAlbumArt.getAlbumArt();
if (imgData != null && imgData.length > 0) {
String mimeType = onkyoAlbumArt.getAlbumArtMimeType();
if (mimeType.isEmpty()) {
mimeType = guessMimeTypeFromData(imgData);
}
updateState(CHANNEL_ALBUM_ART, new RawType(imgData, mimeType));
} else {
updateState(CHANNEL_ALBUM_ART, UnDefType.UNDEF);
}
} catch (IllegalArgumentException e) {
updateState(CHANNEL_ALBUM_ART, UnDefType.UNDEF);
}
onkyoAlbumArt.clearAlbumArt();
}
if (data.startsWith("2-")) {
updateState(CHANNEL_ALBUM_ART_URL, new StringType(data.substring(2, data.length())));
} else if (data.startsWith("n-")) {
updateState(CHANNEL_ALBUM_ART_URL, UnDefType.UNDEF);
} else {
logger.debug("Not supported album art URL type: {}", data.substring(0, 2));
updateState(CHANNEL_ALBUM_ART_URL, UnDefType.UNDEF);
}
}
private void updateNetTitle(String data) {
// first 2 characters is service type
int type = Integer.parseInt(data.substring(0, 2), 16);
ServiceType service = ServiceType.getType(type);
String title = "";
if (data.length() > 21) {
title = data.substring(22, data.length());
}
updateState(CHANNEL_NET_MENU_TITLE,
new StringType(service.toString() + ((title.length() > 0) ? ": " + title : "")));
}
private void updateNetMenu(String data) {
switch (data.charAt(0)) {
case 'U':
String itemData = data.substring(3, data.length());
switch (data.charAt(1)) {
case '0':
updateState(CHANNEL_NET_MENU0, new StringType(itemData));
break;
case '1':
updateState(CHANNEL_NET_MENU1, new StringType(itemData));
break;
case '2':
updateState(CHANNEL_NET_MENU2, new StringType(itemData));
break;
case '3':
updateState(CHANNEL_NET_MENU3, new StringType(itemData));
break;
case '4':
updateState(CHANNEL_NET_MENU4, new StringType(itemData));
break;
case '5':
updateState(CHANNEL_NET_MENU5, new StringType(itemData));
break;
case '6':
updateState(CHANNEL_NET_MENU6, new StringType(itemData));
break;
case '7':
updateState(CHANNEL_NET_MENU7, new StringType(itemData));
break;
case '8':
updateState(CHANNEL_NET_MENU8, new StringType(itemData));
break;
case '9':
updateState(CHANNEL_NET_MENU9, new StringType(itemData));
break;
}
break;
case 'C':
updateMenuPosition(data);
break;
}
}
private void updateMenuPosition(String data) {
char position = data.charAt(1);
int pos = Character.getNumericValue(position);
logger.debug("Updating menu position to {}", pos);
if (pos == -1) {
updateState(CHANNEL_NET_MENU_SELECTION, UnDefType.UNDEF);
} else {
updateState(CHANNEL_NET_MENU_SELECTION, new DecimalType(pos));
}
if (data.endsWith("P")) {
resetNetMenu();
}
}
private void resetNetMenu() {
logger.debug("Reset net menu");
updateState(CHANNEL_NET_MENU0, new StringType("-"));
updateState(CHANNEL_NET_MENU1, new StringType("-"));
updateState(CHANNEL_NET_MENU2, new StringType("-"));
updateState(CHANNEL_NET_MENU3, new StringType("-"));
updateState(CHANNEL_NET_MENU4, new StringType("-"));
updateState(CHANNEL_NET_MENU5, new StringType("-"));
updateState(CHANNEL_NET_MENU6, new StringType("-"));
updateState(CHANNEL_NET_MENU7, new StringType("-"));
updateState(CHANNEL_NET_MENU8, new StringType("-"));
updateState(CHANNEL_NET_MENU9, new StringType("-"));
}
private State convertNetUsbPlayStatus(String data) {
State state = UnDefType.UNDEF;
switch (data.charAt(0)) {
case 'P':
state = PlayPauseType.PLAY;
break;
case 'p':
case 'S':
state = PlayPauseType.PAUSE;
break;
case 'F':
state = RewindFastforwardType.FASTFORWARD;
break;
case 'R':
state = RewindFastforwardType.REWIND;
break;
}
return state;
}
public void sendRawCommand(String command, String value) {
if (connection != null) {
connection.send(command, value);
} else {
logger.debug("Cannot send command to onkyo receiver since the onkyo binding is not initialized");
}
}
private void sendCommand(EiscpCommand deviceCommand) {
if (connection != null) {
connection.send(deviceCommand.getCommand(), deviceCommand.getValue());
} else {
logger.debug("Connect send command to onkyo receiver since the onkyo binding is not initialized");
}
}
private void sendCommand(EiscpCommand deviceCommand, Command command) {
if (connection != null) {
final String cmd = deviceCommand.getCommand();
String valTemplate = deviceCommand.getValue();
String val;
if (command instanceof OnOffType) {
val = String.format(valTemplate, command == OnOffType.ON ? 1 : 0);
} else if (command instanceof StringType) {
val = String.format(valTemplate, command);
} else if (command instanceof DecimalType) {
val = String.format(valTemplate, ((DecimalType) command).intValue());
} else if (command instanceof PercentType) {
val = String.format(valTemplate, ((DecimalType) command).intValue());
} else {
val = valTemplate;
}
logger.debug("Sending command '{}' with value '{}' to Onkyo Receiver @{}", cmd, val,
connection.getConnectionName());
connection.send(cmd, val);
} else {
logger.debug("Connect send command to onkyo receiver since the onkyo binding is not initialized");
}
}
/**
* Check the status of the AVR.
*
* @return
*/
private void checkStatus() {
sendCommand(EiscpCommand.POWER_QUERY);
if (connection != null && connection.isConnected()) {
sendCommand(EiscpCommand.VOLUME_QUERY);
sendCommand(EiscpCommand.SOURCE_QUERY);
sendCommand(EiscpCommand.MUTE_QUERY);
sendCommand(EiscpCommand.NETUSB_TITLE_QUERY);
sendCommand(EiscpCommand.LISTEN_MODE_QUERY);
sendCommand(EiscpCommand.INFO_QUERY);
if (isChannelAvailable(CHANNEL_POWERZONE2)) {
sendCommand(EiscpCommand.ZONE2_POWER_QUERY);
sendCommand(EiscpCommand.ZONE2_VOLUME_QUERY);
sendCommand(EiscpCommand.ZONE2_SOURCE_QUERY);
sendCommand(EiscpCommand.ZONE2_MUTE_QUERY);
}
if (isChannelAvailable(CHANNEL_POWERZONE3)) {
sendCommand(EiscpCommand.ZONE3_POWER_QUERY);
sendCommand(EiscpCommand.ZONE3_VOLUME_QUERY);
sendCommand(EiscpCommand.ZONE3_SOURCE_QUERY);
sendCommand(EiscpCommand.ZONE3_MUTE_QUERY);
}
} else {
updateStatus(ThingStatus.OFFLINE);
}
}
private boolean isChannelAvailable(String channel) {
List<Channel> channels = getThing().getChannels();
for (Channel c : channels) {
if (c.getUID().getId().equals(channel)) {
return true;
}
}
return false;
}
private void handleVolumeSet(EiscpCommand.Zone zone, final State currentValue, final Command command) {
if (command instanceof PercentType) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.VOLUME_SET),
downScaleVolume((PercentType) command));
} else if (command.equals(IncreaseDecreaseType.INCREASE)) {
if (currentValue instanceof PercentType) {
if (((DecimalType) currentValue).intValue() < configuration.volumeLimit) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.VOLUME_UP));
} else {
logger.info("Volume level is limited to {}, ignore volume up command.", configuration.volumeLimit);
}
}
} else if (command.equals(IncreaseDecreaseType.DECREASE)) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.VOLUME_DOWN));
} else if (command.equals(OnOffType.OFF)) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.MUTE_SET), command);
} else if (command.equals(OnOffType.ON)) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.MUTE_SET), command);
} else if (command.equals(RefreshType.REFRESH)) {
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.VOLUME_QUERY));
sendCommand(EiscpCommand.getCommandForZone(zone, EiscpCommand.MUTE_QUERY));
}
}
private State handleReceivedVolume(State volume) {
if (volume instanceof DecimalType) {
return upScaleVolume(((DecimalType) volume));
}
return volume;
}
private PercentType upScaleVolume(DecimalType volume) {
PercentType newVolume = scaleVolumeFromReceiver(volume);
if (configuration.volumeLimit < 100) {
double scaleCoefficient = 100d / configuration.volumeLimit;
PercentType unLimitedVolume = newVolume;
newVolume = new PercentType(((Double) (newVolume.doubleValue() * scaleCoefficient)).intValue());
logger.debug("Up scaled volume level '{}' to '{}'", unLimitedVolume, newVolume);
}
return newVolume;
}
private DecimalType downScaleVolume(PercentType volume) {
PercentType limitedVolume = volume;
if (configuration.volumeLimit < 100) {
double scaleCoefficient = configuration.volumeLimit / 100d;
limitedVolume = new PercentType(((Double) (volume.doubleValue() * scaleCoefficient)).intValue());
logger.debug("Limited volume level '{}' to '{}'", volume, limitedVolume);
}
return scaleVolumeForReceiver(limitedVolume);
}
private DecimalType scaleVolumeForReceiver(PercentType volume) {
return new DecimalType(((Double) (volume.doubleValue() * configuration.volumeScale)).intValue());
}
private PercentType scaleVolumeFromReceiver(DecimalType volume) {
return new PercentType(((Double) (volume.intValue() / configuration.volumeScale)).intValue());
}
@Override
public PercentType getVolume() throws IOException {
if (volumeLevelZone1 instanceof PercentType) {
return (PercentType) volumeLevelZone1;
}
throw new IOException();
}
@Override
public void setVolume(PercentType volume) throws IOException {
handleVolumeSet(EiscpCommand.Zone.ZONE1, volumeLevelZone1, downScaleVolume(volume));
}
private String guessMimeTypeFromData(byte[] data) {
String mimeType = HttpUtil.guessContentTypeFromData(data);
logger.debug("Mime type guess from content: {}", mimeType);
if (mimeType == null) {
mimeType = RawType.DEFAULT_MIME_TYPE;
}
logger.debug("Mime type: {}", mimeType);
return mimeType;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(OnkyoThingActionsService.class);
}
}

View File

@@ -0,0 +1,221 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.onkyo.internal.handler;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.onkyo.internal.OnkyoBindingConstants;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FixedLengthAudioStream;
import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* * The {@link UpnpAudioSinkHandler} is a base class for ThingHandlers for devices which support UPnP playback. It
* implements the AudioSink interface.
* This will allow to register the derived ThingHandler to be registered as a AudioSink in the framework.
*
* @author Paul Frank - Initial contribution
*/
public abstract class UpnpAudioSinkHandler extends BaseThingHandler implements AudioSink, UpnpIOParticipant {
private static final Set<AudioFormat> SUPPORTED_FORMATS = new HashSet<>();
private static final Set<Class<? extends AudioStream>> SUPPORTED_STREAMS = new HashSet<>();
static {
SUPPORTED_FORMATS.add(AudioFormat.WAV);
SUPPORTED_FORMATS.add(AudioFormat.MP3);
SUPPORTED_STREAMS.add(AudioStream.class);
}
private final Logger logger = LoggerFactory.getLogger(getClass());
private AudioHTTPServer audioHTTPServer;
private String callbackUrl;
private UpnpIOService service;
public UpnpAudioSinkHandler(Thing thing, UpnpIOService upnpIOService, AudioHTTPServer audioHTTPServer,
String callbackUrl) {
super(thing);
this.audioHTTPServer = audioHTTPServer;
this.callbackUrl = callbackUrl;
if (upnpIOService != null) {
this.service = upnpIOService;
}
}
protected void handlePlayUri(Command command) {
if (command != null && command instanceof StringType) {
try {
playMedia(command.toString());
} catch (IllegalStateException e) {
logger.warn("Cannot play URI ({})", e.getMessage());
}
}
}
private void playMedia(String url) {
stop();
removeAllTracksFromQueue();
if (!url.startsWith("x-") && (!url.startsWith("http"))) {
url = "x-file-cifs:" + url;
}
setCurrentURI(url, "");
play();
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return SUPPORTED_FORMATS;
}
@Override
public Set<Class<? extends AudioStream>> getSupportedStreams() {
return SUPPORTED_STREAMS;
}
private void stop() {
Map<String, String> inputs = new HashMap<>();
inputs.put("InstanceID", "0");
Map<String, String> result = service.invokeAction(this, "AVTransport", "Stop", inputs);
for (String variable : result.keySet()) {
this.onValueReceived(variable, result.get(variable), "AVTransport");
}
}
private void play() {
Map<String, String> inputs = new HashMap<>();
inputs.put("InstanceID", "0");
inputs.put("Speed", "1");
Map<String, String> result = service.invokeAction(this, "AVTransport", "Play", inputs);
for (String variable : result.keySet()) {
this.onValueReceived(variable, result.get(variable), "AVTransport");
}
}
private void removeAllTracksFromQueue() {
Map<String, String> inputs = new HashMap<>();
inputs.put("InstanceID", "0");
Map<String, String> result = service.invokeAction(this, "AVTransport", "RemoveAllTracksFromQueue", inputs);
for (String variable : result.keySet()) {
this.onValueReceived(variable, result.get(variable), "AVTransport");
}
}
private void setCurrentURI(String uri, String uriMetaData) {
if (uri != null && uriMetaData != null) {
Map<String, String> inputs = new HashMap<>();
try {
inputs.put("InstanceID", "0");
inputs.put("CurrentURI", uri);
inputs.put("CurrentURIMetaData", uriMetaData);
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
Map<String, String> result = service.invokeAction(this, "AVTransport", "SetAVTransportURI", inputs);
for (String variable : result.keySet()) {
this.onValueReceived(variable, result.get(variable), "AVTransport");
}
}
}
@Override
public String getId() {
return getThing().getUID().toString();
}
@Override
public String getLabel(Locale locale) {
return getThing().getLabel();
}
@Override
public void process(@Nullable AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (audioStream == null) {
stop();
return;
}
String url = null;
if (audioStream instanceof URLAudioStream) {
// it is an external URL, the speaker can access it itself and play it.
URLAudioStream urlAudioStream = (URLAudioStream) audioStream;
url = urlAudioStream.getURL();
} else {
if (callbackUrl != null) {
String relativeUrl;
if (audioStream instanceof FixedLengthAudioStream) {
// we serve it on our own HTTP server
relativeUrl = audioHTTPServer.serve((FixedLengthAudioStream) audioStream, 20);
} else {
relativeUrl = audioHTTPServer.serve(audioStream);
}
url = callbackUrl + relativeUrl;
} else {
logger.warn("We do not have any callback url, so onkyo cannot play the audio stream!");
return;
}
}
playMedia(url);
}
@Override
public String getUDN() {
return (String) this.getConfig().get(OnkyoBindingConstants.UDN_PARAMETER);
}
@Override
public void onValueReceived(String variable, String value, String service) {
logger.debug("received variable {} with value {} from service {}", variable, value, service);
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="onkyo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Onkyo Binding</name>
<description>This is the binding for Onkyo receivers.</description>
<author>Paul Frank, Pauli Anttila</author>
<config-description>
<parameter name="callbackUrl" type="text">
<label>Callback URL</label>
<description>url to use for playing notification sounds, e.g. http://192.168.0.2:8080</description>
<required>false</required>
</parameter>
</config-description>
</binding:binding>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:onkyo:config">
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>The IP or host name of the Onkyo Receiver</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" min="1" max="65535">
<label>Port</label>
<description>Port of the Onkyo to control</description>
<default>60128</default>
<advanced>true</advanced>
</parameter>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the Onkyo AVR.</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" min="0">
<label>Refresh Interval</label>
<description>The refresh interval in seconds for polling the receiver (0=disable). Binding receive automatically
updates from
</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
<parameter name="volumeLimit" type="integer" min="0" max="100">
<label>Volume Limit</label>
<description>Limit maximum volume level to defined percentage.</description>
<default>100</default>
<advanced>true</advanced>
</parameter>
<parameter name="volumeScale" type="decimal">
<label>Volume Scale</label>
<description>Applies a scale to the volume.</description>
<default>1</default>
<advanced>true</advanced>
<options>
<option value="1">0-100</option>
<option value="2">0-100 in 0.5 steps</option>
<option value="0.8">0-80</option>
<option value="0.5">0-50</option>
</options>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Channel Groups -->
<channel-group-type id="zone1Controls">
<label>Zone 1 (Main Zone)</label>
<channels>
<channel id="power" typeId="power"/>
<channel id="input" typeId="input"/>
<channel id="volume" typeId="volume"/>
<channel id="mute" typeId="mute"/>
</channels>
</channel-group-type>
<channel-group-type id="zone2Controls">
<label>Zone 2</label>
<channels>
<channel id="power" typeId="power"/>
<channel id="input" typeId="input"/>
<channel id="volume" typeId="volume"/>
<channel id="mute" typeId="mute"/>
</channels>
</channel-group-type>
<channel-group-type id="zone3Controls">
<label>Zone 3</label>
<channels>
<channel id="power" typeId="power"/>
<channel id="input" typeId="input"/>
<channel id="volume" typeId="volume"/>
<channel id="mute" typeId="mute"/>
</channels>
</channel-group-type>
<channel-group-type id="playerControls">
<label>Player</label>
<channels>
<channel id="control" typeId="control"/>
<channel id="currentPlayingTime" typeId="currentPlayingTime"/>
<channel id="title" typeId="title"/>
<channel id="album" typeId="album"/>
<channel id="artist" typeId="artist"/>
<channel id="listenmode" typeId="listenmode"/>
<channel id="playuri" typeId="playuri"/>
<channel id="albumArt" typeId="albumArt"/>
<channel id="albumArtUrl" typeId="albumArtUrl"/>
</channels>
</channel-group-type>
<channel-group-type id="netMenuControls">
<label>Net/USB Menu</label>
<channels>
<channel id="title" typeId="title"/>
<channel id="control" typeId="netControl"/>
<channel id="item0" typeId="menuItem">
<label>Menu Item 0</label>
<description>Net/USB menu item at position 0</description>
</channel>
<channel id="item1" typeId="menuItem">
<label>Menu Item 1</label>
<description>Net/USB menu item at position 1</description>
</channel>
<channel id="item2" typeId="menuItem">
<label>Menu Item 2</label>
<description>Net/USB menu item at position 2</description>
</channel>
<channel id="item3" typeId="menuItem">
<label>Menu Item 3</label>
<description>Net/USB menu item at position 3</description>
</channel>
<channel id="item4" typeId="menuItem">
<label>Menu Item 4</label>
<description>Net/USB menu item at position 4</description>
</channel>
<channel id="item5" typeId="menuItem">
<label>Menu Item 5</label>
<description>Net/USB menu item at position 5</description>
</channel>
<channel id="item6" typeId="menuItem">
<label>Menu Item 6</label>
<description>Net/USB menu item at position 6</description>
</channel>
<channel id="item7" typeId="menuItem">
<label>Menu Item 7</label>
<description>Net/USB menu item at position 7</description>
</channel>
<channel id="item8" typeId="menuItem">
<label>Menu Item 8</label>
<description>Net/USB menu item at position 8</description>
</channel>
<channel id="item9" typeId="menuItem">
<label>Menu Item 9</label>
<description>Net/USB menu item at position 9</description>
</channel>
<channel id="selection" typeId="menuSelection"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Commands -->
<channel-type id="power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Power on/off your device</description>
</channel-type>
<channel-type id="input">
<item-type>Number</item-type>
<label>Input Source</label>
<description>Select the input source of the AVR</description>
<state>
<options>
<option value="0">DVR/VCR</option>
<option value="1">SATELLITE/CABLE</option>
<option value="2">GAME</option>
<option value="3">AUX</option>
<option value="4">GAME2</option>
<option value="5">PC</option>
<option value="16">BLURAY/DVD</option>
<option value="17">STRM BOX</option>
<option value="18">TV</option>
<option value="32">TAPE1</option>
<option value="33">TAPE2</option>
<option value="34">PHONO</option>
<option value="35">CD/TV</option>
<option value="36">TUNER FM</option>
<option value="37">TUNER AM</option>
<option value="38">TUNER</option>
<option value="39">MUSICSERVER</option>
<option value="40">INTERNETRADIO</option>
<option value="41">USB</option>
<option value="42">USB_BACK</option>
<option value="43">NETWORK</option>
<option value="44">USB_TOGGLE</option>
<option value="45">AIRPLAY</option>
<option value="46">BLUETOOTH</option>
<option value="48">MULTICH</option>
<option value="50">SIRIUS</option>
<option value="128">SOURCE</option>
</options>
</state>
</channel-type>
<channel-type id="mute">
<item-type>Switch</item-type>
<label>Mute</label>
<description>Mute/unmute your device</description>
</channel-type>
<channel-type id="volume">
<item-type>Dimmer</item-type>
<label>Volume</label>
<description>Volume of your device</description>
<state min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="control" advanced="true">
<item-type>Player</item-type>
<label>Control</label>
<description>Control the Zone Player, e.g. start/stop/next/previous/ffward/rewind</description>
<category>Player</category>
</channel-type>
<channel-type id="listenmode" advanced="true">
<item-type>Number</item-type>
<label>Listen Mode</label>
<description>Listen mode</description>
<state>
<options>
<option value="0">Stereo</option>
<option value="1">Direct</option>
<option value="3">Game RPG</option>
<option value="5">Game Action</option>
<option value="6">Game Rock</option>
<option value="8">Orchestra</option>
<option value="9">unplugged</option>
<option value="10">Studio Mix</option>
<option value="11">TV Logic</option>
<option value="12">All Channel Stereo</option>
<option value="13">Theater Dimensional</option>
<option value="14">Game-Sports</option>
<option value="15">Mono</option>
<option value="17">Pure Audio</option>
<option value="19">Full Mono</option>
<option value="22">Audyssey DSX</option>
<option value="64">5.1ch Surround</option>
<option value="128">PLII/PLIIx Movie</option>
<option value="129">PLII/PLIIx Music</option>
<option value="130">Neo 6/Neo:X Cinema + DTS:X/Neural:X</option>
<option value="131">Neo 6/Neo:X Music</option>
<option value="134">PLII/PLIIx Game</option>
<option value="160">PLIIx/PLII Movie + Audyssey DSX</option>
<option value="161">PLIIx/PLII Music + Audyssey DSX</option>
<option value="162">PLIIx/PLII Game + Audyssey DSX</option>
<option value="163">Neo Cinema DSX</option>
<option value="164">Neo Music DSX</option>
<option value="165">Neural Surround DSX</option>
<option value="166">Neural Digital DSX</option>
</options>
</state>
</channel-type>
<channel-type id="playuri" advanced="true">
<item-type>String</item-type>
<label>Play URI</label>
<description>Plays a given URI</description>
</channel-type>
<channel-type id="netControl" advanced="true">
<item-type>String</item-type>
<label>Control</label>
<description>Control the USB/Net Menu, e.g. Up/Down/Select/Back/PageUp/PageDown/Select[0-9]</description>
<state>
<options>
<option value="Up">Selection Up</option>
<option value="Down">Selection Down</option>
<option value="Select">Select Entry</option>
<option value="Back">Go Back</option>
<option value="PageUp">Scroll Page Up</option>
<option value="PageDown">Scroll Page Down</option>
<option value="Select0">Select Entry 0</option>
<option value="Select1">Select Entry 1</option>
<option value="Select2">Select Entry 2</option>
<option value="Select3">Select Entry 3</option>
<option value="Select4">Select Entry 4</option>
<option value="Select5">Select Entry 5</option>
<option value="Select6">Select Entry 6</option>
<option value="Select7">Select Entry 7</option>
<option value="Select8">Select Entry 8</option>
<option value="Select9">Select Entry 9</option>
</options>
</state>
</channel-type>
<!-- Onkyo variables -->
<channel-type id="currentPlayingTime" advanced="true">
<item-type>String</item-type>
<label>Playing Time</label>
<description>Current Playing Time</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="title" advanced="true">
<item-type>String</item-type>
<label>Title</label>
<description>Title of the current song</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="album" advanced="true">
<item-type>String</item-type>
<label>Album</label>
<description>Album name of the current song</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="albumArt" advanced="true">
<item-type>Image</item-type>
<label>Album Art</label>
<description>Image of cover art of the current song</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="albumArtUrl" advanced="true">
<item-type>String</item-type>
<label>Album Art Url</label>
<description>Url to the image of cover art of the current song</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="artist" advanced="true">
<item-type>String</item-type>
<label>Artist</label>
<description>Artist name of the current song</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="menuItem" advanced="true">
<item-type>String</item-type>
<label>Menu Item</label>
<description>Net/USB menu item</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="menuSelection" advanced="true">
<item-type>Number</item-type>
<label>Selected Item</label>
<description>Position of the currently selected menu item</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="HT-RC560">
<label>Onkyo HT-RC560 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Generic OnkyoAVR Thing Type -->
<thing-type id="onkyoAVR">
<label>Onkyo AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Onkyo unknown Thing Type -->
<thing-type id="onkyoUnsupported" listed="false">
<label>Unsupported Onkyo AV Receiver</label>
<description>Network enabled Onkyo AV Receivers for models that are not officially supported. You may experience some
odd behaviors.</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR3007">
<label>Onkyo TX-NR3007 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR414">
<label>Onkyo TX-NR414 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR474">
<label>Onkyo TX-NR474 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR509">
<label>Onkyo TX-NR509 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR515">
<label>Onkyo TX-NR515 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR525">
<label>Onkyo TX-NR525 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR535">
<label>Onkyo TX-NR535 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR545">
<label>Onkyo TX-NR545 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR555">
<label>Onkyo TX-NR555 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR575">
<label>Onkyo TX-NR575 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR575E">
<label>Onkyo TX-NR575E AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR616">
<label>Onkyo TX-NR616 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR626">
<label>Onkyo TX-NR626 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR636">
<label>Onkyo TX-NR636 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR646">
<label>Onkyo TX-NR646 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR656">
<label>Onkyo TX-NR656 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR676">
<label>Onkyo TX-NR676 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR686">
<label>Onkyo TX-NR686 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR708">
<label>Onkyo TX-NR708 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR717">
<label>Onkyo TX-NR717 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR727">
<label>Onkyo TX-NR727 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR737">
<label>Onkyo TX-NR737 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR747">
<label>Onkyo TX-NR747 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR809">
<label>Onkyo TX-NR809 AV Receiver</label>
<description>Network enabled Onkyo AV Receivers</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR818">
<label>Onkyo TX-NR818 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR828">
<label>Onkyo TX-NR828 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-NR838">
<label>Onkyo TX-NR838 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="onkyo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="TX-RZ900">
<label>Onkyo TX-RZ900 AV Receiver</label>
<description>Network enabled Onkyo AV Receiver</description>
<channel-groups>
<channel-group typeId="zone1Controls" id="zone1"/>
<channel-group typeId="zone2Controls" id="zone2"/>
<channel-group typeId="zone3Controls" id="zone3"/>
<channel-group typeId="playerControls" id="player"/>
<channel-group typeId="netMenuControls" id="netmenu"/>
</channel-groups>
<config-description-ref uri="thing-type:onkyo:config"/>
</thing-type>
</thing:thing-descriptions>