mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[volumio] Initial contribution (#14525)
Signed-off-by: Michael Loercher <MichaelLoercher@web.de>
This commit is contained in:
parent
1ce6e8775f
commit
de1eebd174
@ -388,6 +388,7 @@
|
||||
/bundles/org.openhab.binding.yamahareceiver/ @davidgraeff @zarusz
|
||||
/bundles/org.openhab.binding.yeelight/ @claell
|
||||
/bundles/org.openhab.binding.yioremote/ @miloit
|
||||
/bundles/org.openhab.binding.volumio/ @miloit
|
||||
/bundles/org.openhab.binding.zoneminder/ @mhilbush
|
||||
/bundles/org.openhab.binding.zway/ @pathec
|
||||
/bundles/org.openhab.io.homekit/ @andylintner @ccutrer @yfre
|
||||
|
@ -1841,6 +1841,11 @@
|
||||
<artifactId>org.openhab.binding.volvooncall</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.volumio</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.warmup</artifactId>
|
||||
|
13
bundles/org.openhab.binding.volumio/NOTICE
Normal file
13
bundles/org.openhab.binding.volumio/NOTICE
Normal 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
|
101
bundles/org.openhab.binding.volumio/README.md
Normal file
101
bundles/org.openhab.binding.volumio/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
# Volumio Binding
|
||||
|
||||
This binding integrates the open-source Music Player [Volumio](https://www.volumio.com).
|
||||
|
||||
## Supported Things
|
||||
|
||||
|
||||
All available Volumio (playback) modes are supported by this binding.
|
||||
|
||||
## Discovery
|
||||
|
||||
The Volumio devices are discovered through mDNS in the local network and all devices are put in the Inbox.
|
||||
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
The binding has the following configuration options, which can be set:
|
||||
|
||||
| Parameter | Name | Description | Required |
|
||||
| ----------- | ---------------- | -------------------------------------------------------------------------- | -------- |
|
||||
| hostname | Hostname | The hostname of the Volumio player. | yes |
|
||||
| port | Port | The port of your volumio2 device (default is 3000) | yes |
|
||||
| protocol | Protocol | The protocol of your volumio2 device (default is http) | yes |
|
||||
| timeout | Timeout | Connection-Timeout in ms | no |
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The Volumio Thing requires the hostname, port and protocol as a configuration value in order for the binding to know how to access it.
|
||||
Additionally, a connection timeout (in ms) can be configured.
|
||||
In the thing file, this looks e.g. like
|
||||
|
||||
```java
|
||||
Thing volumio:player:VolumioLivingRoom "Volumio" @ "Living Room" [hostname="volumio.local", protocol="http"]
|
||||
```
|
||||
|
||||
### `sample` Thing Configuration
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|-----------------|---------|---------------------------------------|---------|----------|----------|
|
||||
| hostname | text | The hostname of the Volumio player. | N/A | yes | no |
|
||||
| port | text | The port of your Volumio device. | 3000 | yes | no |
|
||||
| protocol | text | The protocol of your Volumio device. | http | yes | no |
|
||||
| timeout | integer | Connection-Timeout in ms. | 5000 | no | yes |
|
||||
|
||||
## Channels
|
||||
|
||||
The devices support the following channels:
|
||||
|
||||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-------------------|--------|------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| title | String | R | Title of the song currently playing. |
|
||||
| artist | String | R | Name of the artist currently playing. |
|
||||
| album | String | R | Name of the album currently playing. |
|
||||
| volume | Dimmer | RW | Set or get the master volume. |
|
||||
| player | Player | RW | The State channel contains state of the Volumio Player. |
|
||||
| albumArt | Image | R | Cover Art for the currently played track. |
|
||||
| track-type | String | R | Tracktype of the currently played track. |
|
||||
| play-radiostream | String | RW | Play the given radio stream. |
|
||||
| play-playlist | String | RW | Playback a playlist identified by its name. |
|
||||
| clear-queue | Switch | RW | Clear the current queue. |
|
||||
| play-uri | Switch | RW | Play the stream at given uri. |
|
||||
| play-file | Switch | RW | Play a file, located on your Volumio device at the given absolute path, e.g."mnt/INTERNAL/song.mp3" |
|
||||
| random | Switch | RW | Activate random mode. |
|
||||
| repeat | Switch | RW | Activate repeat mode. |
|
||||
| system-command | Switch | RW | Sends a system command to Volumio. This allows to shutdown/reboot Volumio. Use "Shutdown"/"Reboot" as String command.|
|
||||
| stop-command | Switch | RW | Sends a Stop Command to Volumio. This allows to stop the player. Use "stop" as string command. |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```java
|
||||
Thing volumio:player:VolumioLivingRoom "Volumio" @ "Living Room" [hostname="volumio.local", protocol="http"]
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```java
|
||||
String Volumio_CurrentTitle "Current Title [%s]" <musicnote> {channel="volumio:player:VolumioLivingRoom:title"}
|
||||
String Volumio_CurrentArtist "Current Artist [%s]" {channel="volumio:player:VolumioLivingRoom:artist"}
|
||||
String Volumio_CurrentAlbum "Current Album [%s]" {channel="volumio:player:VolumioLivingRoom:album"}
|
||||
Dimmer Volumio_CurrentVolume "Current Volume [%.1f %%]" <soundvolume> {channel="volumio:player:VolumioLivingRoom:volume"}
|
||||
Player Volumio "Current Status [%s]" <volumiologo> {channel="volumio:player:VolumioLivingRoom:player"}
|
||||
String Volumio_CurrentTrackType "Current Track Type [%s]" <musicnote> {channel="volumio:player:VolumioLivingRoom:track-type"}
|
||||
```
|
||||
|
||||
demo.sitemap:
|
||||
|
||||
```perl
|
||||
sitemap demo label="Main Menu"
|
||||
{
|
||||
Frame label="Volumio" {
|
||||
Slider item=Volumio_CurrentVolume
|
||||
Text item=Volumio
|
||||
Text item=Volumio_CurrentTitle
|
||||
}
|
||||
}
|
||||
```
|
51
bundles/org.openhab.binding.volumio/pom.xml
Normal file
51
bundles/org.openhab.binding.volumio/pom.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://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>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.volumio</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Volumio Binding</name>
|
||||
<properties>
|
||||
<bnd.importpackage>org.apache.http.*;io.socket.thread;io.socket.engineio.client;io.socket.emitter;android.*;resolution:=optional,com.android.org.*;resolution:=optional,dalvik.*;resolution:=optional,javax.annotation.meta.*;resolution:=optional,org.apache.harmony.*;resolution:=optional,org.conscrypt.*;resolution:=optional,sun.security.*;resolution:=optional</bnd.importpackage>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.openhab.osgiify</groupId>
|
||||
<artifactId>io.socket.socket.io-client</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.osgiify</groupId>
|
||||
<artifactId>io.socket.engine.io-client</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.okhttp</artifactId>
|
||||
<version>3.8.1_1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.servicemix.bundles</groupId>
|
||||
<artifactId>org.apache.servicemix.bundles.okio</artifactId>
|
||||
<version>1.13.0_1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20230227</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.volumio-${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-volumio" description="Volumio Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.volumio/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link VolumioBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "volumio";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_VOLUMIO = new ThingTypeUID(BINDING_ID, "player");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_TITLE = "title";
|
||||
public static final String CHANNEL_ARTIST = "artist";
|
||||
public static final String CHANNEL_ALBUM = "album";
|
||||
public static final String CHANNEL_VOLUME = "volume";
|
||||
public static final String CHANNEL_PLAYER = "player";
|
||||
public static final String CHANNEL_COVER_ART = "album-art";
|
||||
public static final String CHANNEL_TRACK_TYPE = "track-type";
|
||||
public static final String CHANNEL_PLAY_RADIO_STREAM = "play-radiostream";
|
||||
public static final String CHANNEL_PLAY_PLAYLIST = "play-playlist";
|
||||
public static final String CHANNEL_CLEAR_QUEUE = "clear-queue";
|
||||
public static final String CHANNEL_PLAY_RANDOM = "random";
|
||||
public static final String CHANNEL_PLAY_REPEAT = "repeat";
|
||||
public static final String CHANNEL_PLAY_URI = "play-uri";
|
||||
public static final String CHANNEL_PLAY_FILE = "play-file";
|
||||
public static final String CHANNEL_SYSTEM_COMMAND = "system-command";
|
||||
public static final String CHANNEL_STOP = "stop-command";
|
||||
|
||||
// discovery properties
|
||||
public static final String DISCOVERY_SERVICE_TYPE = "_Volumio._tcp.local.";
|
||||
public static final String DISCOVERY_NAME_PROPERTY = "volumioName";
|
||||
public static final String DISCOVERY_UUID_PROPERTY = "UUID";
|
||||
|
||||
// config
|
||||
public static final String CONFIG_PROPERTY_HOSTNAME = "hostname";
|
||||
public static final String CONFIG_PROPERTY_PORT = "port";
|
||||
public static final String CONFIG_PROPERTY_PROTOCOL = "protocol";
|
||||
public static final String CONFIG_PROPERTY_TIMEOUT = "timeout";
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VolumioConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Patrick Sernetz - Initial contribution
|
||||
* @author Chris Wohlbrecht - Adapt for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioConfiguration {
|
||||
|
||||
private String hostName = "";
|
||||
|
||||
private int port;
|
||||
|
||||
private String protocol = "";
|
||||
|
||||
private int timeout;
|
||||
|
||||
public String getHost() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.hostName = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.binding.volumio.internal.mapping.VolumioData;
|
||||
import org.openhab.binding.volumio.internal.mapping.VolumioEvents;
|
||||
import org.openhab.binding.volumio.internal.mapping.VolumioServiceTypes;
|
||||
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.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.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.socket.client.Socket;
|
||||
import io.socket.emitter.Emitter;
|
||||
|
||||
/**
|
||||
* The {@link VolumioHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumioHandler.class);
|
||||
|
||||
private @Nullable VolumioService volumio;
|
||||
|
||||
private final VolumioData state = new VolumioData();
|
||||
|
||||
public VolumioHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("channelUID: {}", channelUID);
|
||||
|
||||
if (volumio == null) {
|
||||
logger.debug("Ignoring command {} = {} because device is offline.", channelUID.getId(), command);
|
||||
if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "device is offline");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case VolumioBindingConstants.CHANNEL_PLAYER:
|
||||
handlePlaybackCommands(command);
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_VOLUME:
|
||||
handleVolumeCommand(command);
|
||||
break;
|
||||
|
||||
case VolumioBindingConstants.CHANNEL_ARTIST:
|
||||
case VolumioBindingConstants.CHANNEL_ALBUM:
|
||||
case VolumioBindingConstants.CHANNEL_TRACK_TYPE:
|
||||
case VolumioBindingConstants.CHANNEL_TITLE:
|
||||
break;
|
||||
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_RADIO_STREAM:
|
||||
if (command instanceof StringType) {
|
||||
final String uri = command.toFullString();
|
||||
volumio.replacePlay(uri, "Radio", VolumioServiceTypes.WEBRADIO);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_URI:
|
||||
if (command instanceof StringType) {
|
||||
final String uri = command.toFullString();
|
||||
volumio.replacePlay(uri, "URI", VolumioServiceTypes.WEBRADIO);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_FILE:
|
||||
if (command instanceof StringType) {
|
||||
final String uri = command.toFullString();
|
||||
volumio.replacePlay(uri, "", VolumioServiceTypes.MPD);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_PLAYLIST:
|
||||
if (command instanceof StringType) {
|
||||
final String playlistName = command.toFullString();
|
||||
volumio.playPlaylist(playlistName);
|
||||
}
|
||||
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_CLEAR_QUEUE:
|
||||
if ((command instanceof OnOffType) && (command == OnOffType.ON)) {
|
||||
volumio.clearQueue();
|
||||
// Make it feel like a toggle button ...
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_RANDOM:
|
||||
if (command instanceof OnOffType) {
|
||||
boolean enableRandom = command == OnOffType.ON;
|
||||
volumio.setRandom(enableRandom);
|
||||
}
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_PLAY_REPEAT:
|
||||
if (command instanceof OnOffType) {
|
||||
boolean enableRepeat = command == OnOffType.ON;
|
||||
volumio.setRepeat(enableRepeat);
|
||||
}
|
||||
break;
|
||||
case "REFRESH":
|
||||
logger.debug("Called Refresh");
|
||||
volumio.getState();
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_SYSTEM_COMMAND:
|
||||
if (command instanceof StringType) {
|
||||
sendSystemCommand(command);
|
||||
updateState(VolumioBindingConstants.CHANNEL_SYSTEM_COMMAND, UnDefType.UNDEF);
|
||||
} else if (RefreshType.REFRESH == command) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_SYSTEM_COMMAND, UnDefType.UNDEF);
|
||||
}
|
||||
break;
|
||||
case VolumioBindingConstants.CHANNEL_STOP:
|
||||
if (command instanceof StringType) {
|
||||
handleStopCommand(command);
|
||||
updateState(VolumioBindingConstants.CHANNEL_STOP, UnDefType.UNDEF);
|
||||
} else if (RefreshType.REFRESH == command) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_STOP, UnDefType.UNDEF);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown channel: {}", channelUID.getId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSystemCommand(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
volumio.sendSystemCommand(command.toString());
|
||||
updateState(VolumioBindingConstants.CHANNEL_SYSTEM_COMMAND, UnDefType.UNDEF);
|
||||
} else if (command.equals(RefreshType.REFRESH)) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_SYSTEM_COMMAND, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all channel of thing to UNDEF during connection.
|
||||
*/
|
||||
private void clearChannels() {
|
||||
for (Channel channel : getThing().getChannels()) {
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleVolumeCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
volumio.setVolume((PercentType) command);
|
||||
} else if (command instanceof RefreshType) {
|
||||
volumio.getState();
|
||||
} else {
|
||||
logger.error("Command is not handled");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStopCommand(Command command) {
|
||||
if (command instanceof StringType) {
|
||||
volumio.stop();
|
||||
updateState(VolumioBindingConstants.CHANNEL_STOP, UnDefType.UNDEF);
|
||||
} else if (command.equals(RefreshType.REFRESH)) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_STOP, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePlaybackCommands(Command command) {
|
||||
if (command instanceof PlayPauseType playPauseCmd) {
|
||||
switch (playPauseCmd) {
|
||||
case PLAY:
|
||||
volumio.play();
|
||||
break;
|
||||
case PAUSE:
|
||||
volumio.pause();
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof NextPreviousType nextPreviousType) {
|
||||
switch (nextPreviousType) {
|
||||
case PREVIOUS:
|
||||
volumio.previous();
|
||||
break;
|
||||
case NEXT:
|
||||
volumio.next();
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof RewindFastforwardType fastForwardType) {
|
||||
switch (fastForwardType) {
|
||||
case FASTFORWARD:
|
||||
case REWIND:
|
||||
logger.warn("Not implemented yet");
|
||||
break;
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
volumio.getState();
|
||||
} else {
|
||||
logger.error("Command is not handled: {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind default listeners to volumio session.
|
||||
* - EVENT_CONNECT - Connection to volumio was established
|
||||
* - EVENT_DISCONNECT - Connection was disconnected
|
||||
* - PUSH.STATE -
|
||||
*/
|
||||
private void bindDefaultListener() {
|
||||
volumio.on(Socket.EVENT_CONNECT, connectListener());
|
||||
volumio.on(Socket.EVENT_DISCONNECT, disconnectListener());
|
||||
volumio.on(VolumioEvents.PUSH_STATE, pushStateListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the configuration and connect to volumio device. The Volumio impl. is
|
||||
* async so it should not block the process in any way.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
String hostname = (String) getThing().getConfiguration().get(VolumioBindingConstants.CONFIG_PROPERTY_HOSTNAME);
|
||||
int port = ((BigDecimal) getThing().getConfiguration().get(VolumioBindingConstants.CONFIG_PROPERTY_PORT))
|
||||
.intValueExact();
|
||||
String protocol = (String) getThing().getConfiguration().get(VolumioBindingConstants.CONFIG_PROPERTY_PROTOCOL);
|
||||
int timeout = ((BigDecimal) getThing().getConfiguration().get(VolumioBindingConstants.CONFIG_PROPERTY_TIMEOUT))
|
||||
.intValueExact();
|
||||
|
||||
if (hostname == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Configuration incomplete, missing hostname");
|
||||
} else if (protocol == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Configuration incomplete, missing protocol");
|
||||
} else {
|
||||
logger.debug("Trying to connect to Volumio on {}://{}:{}", protocol, hostname, port);
|
||||
try {
|
||||
volumio = new VolumioService(protocol, hostname, port, timeout);
|
||||
clearChannels();
|
||||
bindDefaultListener();
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
volumio.connect();
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (volumio != null) {
|
||||
scheduler.schedule(() -> {
|
||||
if (volumio.isConnected()) {
|
||||
logger.warn("Timeout during disconnect event");
|
||||
} else {
|
||||
volumio.close();
|
||||
}
|
||||
clearChannels();
|
||||
}, 30, TimeUnit.SECONDS);
|
||||
|
||||
volumio.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener **/
|
||||
|
||||
/**
|
||||
* As soon as the Connect Listener is executed
|
||||
* the ThingStatus is set to ONLINE.
|
||||
*/
|
||||
private Emitter.Listener connectListener() {
|
||||
return arg -> updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as the Disconnect Listener is executed
|
||||
* the ThingStatus is set to OFFLINE.
|
||||
*/
|
||||
private Emitter.Listener disconnectListener() {
|
||||
return arg0 -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* On received a pushState Event, the ThingChannels are
|
||||
* updated if there is a change and they are linked.
|
||||
*/
|
||||
private Emitter.Listener pushStateListener() {
|
||||
return data -> {
|
||||
try {
|
||||
JSONObject jsonObject = (JSONObject) data[0];
|
||||
logger.debug("{}", jsonObject.toString());
|
||||
state.update(jsonObject);
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_TITLE) && state.isTitleDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_TITLE, state.getTitle());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_ARTIST) && state.isArtistDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_ARTIST, state.getArtist());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_ALBUM) && state.isAlbumDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_ALBUM, state.getAlbum());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_VOLUME) && state.isVolumeDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_VOLUME, state.getVolume());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_PLAYER) && state.isStateDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_PLAYER, state.getState());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_TRACK_TYPE) && state.isTrackTypeDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_TRACK_TYPE, state.getTrackType());
|
||||
}
|
||||
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_PLAY_RANDOM) && state.isRandomDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_PLAY_RANDOM, state.getRandom());
|
||||
}
|
||||
if (isLinked(VolumioBindingConstants.CHANNEL_PLAY_REPEAT) && state.isRepeatDirty()) {
|
||||
updateState(VolumioBindingConstants.CHANNEL_PLAY_REPEAT, state.getRepeat());
|
||||
}
|
||||
/**
|
||||
* if (isLinked(CHANNEL_COVER_ART) && state.isCoverArtDirty()) {
|
||||
* updateState(CHANNEL_COVER_ART, state.getCoverArt());
|
||||
* }
|
||||
*/
|
||||
} catch (JSONException e) {
|
||||
logger.error("Could not refresh channel: {}", e.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link VolumioHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.volumio", service = ThingHandlerFactory.class)
|
||||
public class VolumioHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
|
||||
.of(VolumioBindingConstants.THING_TYPE_VOLUMIO);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (VolumioBindingConstants.THING_TYPE_VOLUMIO.equals(thingTypeUID)) {
|
||||
return new VolumioHandler(thing);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.binding.volumio.internal.mapping.VolumioCommands;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.socket.client.IO;
|
||||
import io.socket.client.Socket;
|
||||
import io.socket.emitter.Emitter;
|
||||
|
||||
/**
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumioService.class);
|
||||
|
||||
private final Socket socket;
|
||||
|
||||
private boolean connected;
|
||||
|
||||
public VolumioService(String protocol, String hostname, int port, int timeout)
|
||||
throws URISyntaxException, UnknownHostException {
|
||||
String uriString = String.format("%s://%s:%d", protocol, hostname, port);
|
||||
|
||||
URI destUri = new URI(uriString);
|
||||
|
||||
IO.Options opts = new IO.Options();
|
||||
opts.reconnection = true;
|
||||
opts.reconnectionDelay = 1000 * 30;
|
||||
opts.reconnectionDelayMax = 1000 * 60;
|
||||
opts.timeout = timeout;
|
||||
|
||||
// Connection to mdns endpoint is only available after fetching ip.
|
||||
InetAddress ipaddress = InetAddress.getByName(hostname);
|
||||
logger.debug("Resolving {} to IP {}", hostname, ipaddress.getHostAddress());
|
||||
|
||||
socket = IO.socket(destUri, opts);
|
||||
|
||||
bindDefaultEvents(hostname);
|
||||
}
|
||||
|
||||
private void bindDefaultEvents(String hostname) {
|
||||
socket.on(Socket.EVENT_CONNECTING, arg0 -> logger.debug("Trying to connect to Volumio on {}", hostname));
|
||||
|
||||
socket.on(Socket.EVENT_RECONNECTING, arg0 -> logger.debug("Trying to reconnect to Volumio on {}", hostname));
|
||||
|
||||
socket.on(Socket.EVENT_CONNECT_ERROR, arg0 -> logger.error("Could not connect to Volumio on {}", hostname));
|
||||
|
||||
socket.on(Socket.EVENT_CONNECT_TIMEOUT,
|
||||
arg0 -> logger.error("Timedout while conntecting to Volumio on {}", hostname));
|
||||
|
||||
socket.on(Socket.EVENT_CONNECT, arg0 -> {
|
||||
logger.info("Connected to Volumio on {}", hostname);
|
||||
setConnected(true);
|
||||
|
||||
}).on(Socket.EVENT_DISCONNECT, arg0 -> {
|
||||
logger.warn("Disconnected from Volumio on {}", hostname);
|
||||
setConnected(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void connect() throws InterruptedException {
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
socket.disconnect();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
socket.off();
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public void on(String eventName, Emitter.Listener listener) {
|
||||
socket.on(eventName, listener);
|
||||
}
|
||||
|
||||
public void once(String eventName, Emitter.Listener listener) {
|
||||
socket.once(eventName, listener);
|
||||
}
|
||||
|
||||
public void getState() {
|
||||
socket.emit(VolumioCommands.GET_STATE);
|
||||
}
|
||||
|
||||
public void play() {
|
||||
socket.emit(VolumioCommands.PLAY);
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
socket.emit(VolumioCommands.PAUSE);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
socket.emit(VolumioCommands.STOP);
|
||||
}
|
||||
|
||||
public void play(Integer index) {
|
||||
socket.emit(VolumioCommands.PLAY, index);
|
||||
}
|
||||
|
||||
public void next() {
|
||||
socket.emit(VolumioCommands.NEXT);
|
||||
}
|
||||
|
||||
public void previous() {
|
||||
socket.emit(VolumioCommands.PREVIOUS);
|
||||
}
|
||||
|
||||
public void setVolume(PercentType level) {
|
||||
socket.emit(VolumioCommands.VOLUME, level.intValue());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
socket.emit(VolumioCommands.SHUTDOWN);
|
||||
}
|
||||
|
||||
public void reboot() {
|
||||
socket.emit(VolumioCommands.REBOOT);
|
||||
}
|
||||
|
||||
public void playPlaylist(String playlistName) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("name", playlistName);
|
||||
|
||||
socket.emit(VolumioCommands.PLAY_PLAYLIST, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void clearQueue() {
|
||||
socket.emit(VolumioCommands.CLEAR_QUEUE);
|
||||
}
|
||||
|
||||
public void setRandom(boolean val) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("value", val);
|
||||
|
||||
socket.emit(VolumioCommands.RANDOM, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void setRepeat(boolean val) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("value", val);
|
||||
|
||||
socket.emit(VolumioCommands.REPEAT, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void playFavorites(String favoriteName) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("name", favoriteName);
|
||||
|
||||
socket.emit(VolumioCommands.PLAY_FAVOURITES, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a radio station from volumio´s Radio Favourites identifed by
|
||||
* its index.
|
||||
*/
|
||||
public void playRadioFavourite(final Integer index) {
|
||||
logger.debug("socket.emit({})", VolumioCommands.PLAY_RADIO_FAVOURITES);
|
||||
|
||||
socket.once("pushPlayRadioFavourites", arg -> play(index));
|
||||
|
||||
socket.emit(VolumioCommands.PLAY_RADIO_FAVOURITES);
|
||||
}
|
||||
|
||||
public void playURI(String uri) {
|
||||
JSONObject item = new JSONObject();
|
||||
logger.debug("PlayURI: {}", uri);
|
||||
try {
|
||||
item.put("uri", uri);
|
||||
|
||||
socket.emit(VolumioCommands.PLAY, uri);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void addPlay(String uri, String title, String serviceType) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("uri", uri);
|
||||
item.put("title", title);
|
||||
item.put("service", serviceType);
|
||||
|
||||
socket.emit(VolumioCommands.ADD_PLAY, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void replacePlay(String uri, String title, String serviceType) {
|
||||
JSONObject item = new JSONObject();
|
||||
|
||||
try {
|
||||
item.put("uri", uri);
|
||||
item.put("title", title);
|
||||
item.put("service", serviceType);
|
||||
|
||||
socket.emit(VolumioCommands.REPLACE_AND_PLAY, item);
|
||||
} catch (JSONException e) {
|
||||
logger.error("The following error occurred {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
public void setConnected(boolean status) {
|
||||
this.connected = status;
|
||||
}
|
||||
|
||||
public void sendSystemCommand(String string) {
|
||||
logger.warn("Jukebox Command: {}", string);
|
||||
switch (string) {
|
||||
case VolumioCommands.SHUTDOWN:
|
||||
shutdown();
|
||||
break;
|
||||
case VolumioCommands.REBOOT:
|
||||
reboot();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.volumio.internal.VolumioBindingConstants.THING_TYPE_VOLUMIO;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volumio.internal.VolumioBindingConstants;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Patrick Sernetz - Initial contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "discovery.volumio")
|
||||
public class VolumioDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumioDiscoveryParticipant.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Set.of(THING_TYPE_VOLUMIO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return VolumioBindingConstants.DISCOVERY_SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(ServiceInfo serviceInfo) {
|
||||
String volumioName = serviceInfo.getPropertyString(VolumioBindingConstants.DISCOVERY_NAME_PROPERTY);
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
ThingUID thingUID = getThingUID(serviceInfo);
|
||||
|
||||
logger.debug("Service Device: {}", serviceInfo);
|
||||
logger.debug("Thing UID: {}", thingUID);
|
||||
|
||||
DiscoveryResult discoveryResult = null;
|
||||
if (thingUID != null) {
|
||||
properties.put("hostname", serviceInfo.getServer());
|
||||
properties.put("port", serviceInfo.getPort());
|
||||
properties.put("protocol", "http");
|
||||
|
||||
discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(volumioName)
|
||||
.build();
|
||||
logger.debug("DiscoveryResult: {}", discoveryResult);
|
||||
}
|
||||
return discoveryResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo serviceInfo) {
|
||||
Collections.list(serviceInfo.getPropertyNames()).forEach(s -> logger.debug("PropertyName: {}", s));
|
||||
|
||||
String volumioName = serviceInfo.getPropertyString("volumioName");
|
||||
if (volumioName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String uuid = serviceInfo.getPropertyString("UUID");
|
||||
if (uuid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String uuidAndServername = String.format("%s-%s", uuid, volumioName);
|
||||
logger.debug("return new ThingUID({}, {});", THING_TYPE_VOLUMIO, uuidAndServername);
|
||||
return new ThingUID(THING_TYPE_VOLUMIO, uuidAndServername);
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal.mapping;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @see https://github.com/volumio/Volumio2-UI/blob/master/src/app/services/player.service.js
|
||||
* @see https://github.com/volumio/Volumio2/blob/master/app/plugins/user_interface/websocket/index.js
|
||||
*
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioCommands {
|
||||
|
||||
/* Player Status */
|
||||
|
||||
public static final String GET_STATE = "get-state";
|
||||
|
||||
/* Player Controls */
|
||||
|
||||
public static final String PLAY = "play";
|
||||
|
||||
public static final String PAUSE = "pause";
|
||||
|
||||
public static final String STOP = "stop";
|
||||
|
||||
public static final String PREVIOUS = "prev";
|
||||
|
||||
public static final String NEXT = "next";
|
||||
|
||||
public static final String SEEK = "seek";
|
||||
|
||||
public static final String RANDOM = "set-random";
|
||||
|
||||
public static final String REPEAT = "set-repeat";
|
||||
|
||||
/* Search */
|
||||
|
||||
public static final String SEARCH = "search";
|
||||
|
||||
/* Volume */
|
||||
|
||||
public static final String VOLUME = "volume";
|
||||
|
||||
public static final String MUTE = "mute";
|
||||
|
||||
public static final String UNMUTE = "unmute";
|
||||
|
||||
/* MultiRoom */
|
||||
|
||||
public static final String GET_MULTIROOM_DEVICES = "get-multi-room-devices";
|
||||
|
||||
/* Queue */
|
||||
|
||||
/**
|
||||
* Replace the complete queue and play add/play the delivered entry.
|
||||
*/
|
||||
public static final String REPLACE_AND_PLAY = "replace-and-play";
|
||||
|
||||
public static final String ADD_PLAY = "addPlay";
|
||||
|
||||
public static final String CLEAR_QUEUE = "clear-queue";
|
||||
|
||||
/* ... */
|
||||
public static final String SHUTDOWN = "shutdown";
|
||||
|
||||
public static final String REBOOT = "reboot";
|
||||
|
||||
public static final String PLAY_PLAYLIST = "play-playlist";
|
||||
|
||||
public static final String PLAY_FAVOURITES = "play-favourites";
|
||||
|
||||
public static final String PLAY_RADIO_FAVOURITES = "play-radio-favourites";
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal.mapping;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.binding.volumio.internal.VolumioBindingConstants;
|
||||
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.StringType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VolumioData} class defines state data of volumio.
|
||||
*
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Chris Wohlbrecht - Adaption for openHAB 3
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioData {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolumioData.class);
|
||||
|
||||
private String title = "";
|
||||
private boolean titleDirty;
|
||||
|
||||
private String album = "";
|
||||
private boolean albumDirty;
|
||||
|
||||
private String artist = "";
|
||||
private boolean artistDirty;
|
||||
|
||||
private int volume = 0;
|
||||
private boolean volumeDirty;
|
||||
|
||||
private String state = "";
|
||||
private boolean stateDirty;
|
||||
|
||||
private String trackType = "";
|
||||
private boolean trackTypeDirty;
|
||||
|
||||
private String position = "";
|
||||
private boolean positionDirty;
|
||||
|
||||
private byte @Nullable [] coverArt = null;
|
||||
private String coverArtUrl = "";
|
||||
private boolean coverArtDirty;
|
||||
|
||||
private boolean repeat = false;
|
||||
private boolean repeatDirty;
|
||||
|
||||
private boolean random = false;
|
||||
private boolean randomDirty;
|
||||
|
||||
public void update(JSONObject jsonObject) throws JSONException {
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_TITLE)) {
|
||||
setTitle(jsonObject.getString(VolumioBindingConstants.CHANNEL_TITLE));
|
||||
} else {
|
||||
setTitle("");
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_ALBUM)
|
||||
&& !jsonObject.isNull(VolumioBindingConstants.CHANNEL_ALBUM)) {
|
||||
setAlbum(jsonObject.getString(VolumioBindingConstants.CHANNEL_ALBUM));
|
||||
} else {
|
||||
setAlbum("");
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_VOLUME)) {
|
||||
setVolume(jsonObject.getInt(VolumioBindingConstants.CHANNEL_VOLUME));
|
||||
} else {
|
||||
setVolume(0);
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_ARTIST)) {
|
||||
setArtist(jsonObject.getString(VolumioBindingConstants.CHANNEL_ARTIST));
|
||||
} else {
|
||||
setArtist("");
|
||||
}
|
||||
|
||||
/* Special */
|
||||
if (jsonObject.has("status")) {
|
||||
setState(jsonObject.getString("status"));
|
||||
} else {
|
||||
setState("pause");
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_TRACK_TYPE)) {
|
||||
setTrackType(jsonObject.getString(VolumioBindingConstants.CHANNEL_TRACK_TYPE));
|
||||
} else {
|
||||
setTrackType("");
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_COVER_ART)
|
||||
&& !jsonObject.isNull(VolumioBindingConstants.CHANNEL_COVER_ART)) {
|
||||
setCoverArt(jsonObject.getString(VolumioBindingConstants.CHANNEL_COVER_ART));
|
||||
} else {
|
||||
setCoverArt(null);
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_PLAY_RANDOM)
|
||||
&& !jsonObject.isNull(VolumioBindingConstants.CHANNEL_PLAY_RANDOM)) {
|
||||
setRandom(jsonObject.getBoolean(VolumioBindingConstants.CHANNEL_PLAY_RANDOM));
|
||||
} else {
|
||||
setRandom(false);
|
||||
}
|
||||
|
||||
if (jsonObject.has(VolumioBindingConstants.CHANNEL_PLAY_REPEAT)
|
||||
&& !jsonObject.isNull(VolumioBindingConstants.CHANNEL_PLAY_REPEAT)) {
|
||||
setRepeat(jsonObject.getBoolean(VolumioBindingConstants.CHANNEL_PLAY_REPEAT));
|
||||
} else {
|
||||
setRepeat(false);
|
||||
}
|
||||
}
|
||||
|
||||
public StringType getTitle() {
|
||||
return new StringType(title);
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
if (!title.equals(this.title)) {
|
||||
this.title = title;
|
||||
this.titleDirty = true;
|
||||
} else {
|
||||
this.titleDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public StringType getAlbum() {
|
||||
return new StringType(album);
|
||||
}
|
||||
|
||||
public void setAlbum(String album) {
|
||||
if ("null".equals(album)) {
|
||||
album = "";
|
||||
}
|
||||
|
||||
if (!album.equals(this.album)) {
|
||||
this.album = album;
|
||||
this.albumDirty = true;
|
||||
} else {
|
||||
this.albumDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public StringType getArtist() {
|
||||
return new StringType(artist);
|
||||
}
|
||||
|
||||
public void setArtist(String artist) {
|
||||
if ("null".equals(artist)) {
|
||||
this.artist = "";
|
||||
}
|
||||
|
||||
if (!artist.equals(this.artist)) {
|
||||
this.artist = artist;
|
||||
this.artistDirty = true;
|
||||
} else {
|
||||
this.artistDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public PercentType getVolume() {
|
||||
return new PercentType(volume);
|
||||
}
|
||||
|
||||
public void setVolume(int volume) {
|
||||
if (volume != this.volume) {
|
||||
this.volume = volume;
|
||||
this.volumeDirty = true;
|
||||
} else {
|
||||
this.volumeDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
if (!state.equals(this.state)) {
|
||||
this.state = state;
|
||||
this.stateDirty = true;
|
||||
} else {
|
||||
this.stateDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public PlayPauseType getState() {
|
||||
PlayPauseType playPauseStatus;
|
||||
|
||||
if ("play".equals(state)) {
|
||||
playPauseStatus = PlayPauseType.PLAY;
|
||||
} else {
|
||||
playPauseStatus = PlayPauseType.PAUSE;
|
||||
}
|
||||
|
||||
return playPauseStatus;
|
||||
}
|
||||
|
||||
public void setTrackType(String trackType) {
|
||||
if (!trackType.equals(this.trackType)) {
|
||||
this.trackType = trackType;
|
||||
this.trackTypeDirty = true;
|
||||
} else {
|
||||
this.trackTypeDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public StringType getTrackType() {
|
||||
return new StringType(trackType);
|
||||
}
|
||||
|
||||
public void setPosition(String position) {
|
||||
if (!position.equals(this.position)) {
|
||||
this.position = position;
|
||||
this.positionDirty = true;
|
||||
} else {
|
||||
this.positionDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCoverArt(@Nullable String coverArtUrl) {
|
||||
if (coverArtUrl != null) {
|
||||
if (!Objects.equals(coverArtUrl, this.coverArtUrl)) {
|
||||
if (!coverArtUrl.startsWith("http")) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URL(coverArtUrl);
|
||||
URLConnection connection = url.openConnection();
|
||||
InputStream inStream = null;
|
||||
inStream = connection.getInputStream();
|
||||
coverArt = inputStreamToByte(inStream);
|
||||
} catch (IOException ioe) {
|
||||
coverArt = null;
|
||||
}
|
||||
this.coverArtDirty = true;
|
||||
} else {
|
||||
this.coverArtDirty = false;
|
||||
}
|
||||
} else {
|
||||
coverArt = null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte @Nullable [] inputStreamToByte(InputStream is) {
|
||||
byte @Nullable [] imgdata = null;
|
||||
try (ByteArrayOutputStream bytestream = new ByteArrayOutputStream()) {
|
||||
int ch;
|
||||
while ((ch = is.read()) != -1) {
|
||||
bytestream.write(ch);
|
||||
}
|
||||
imgdata = bytestream.toByteArray();
|
||||
return imgdata;
|
||||
} catch (Exception e) {
|
||||
logger.error("Could not open or read input stream {}", e.getMessage());
|
||||
}
|
||||
|
||||
return imgdata;
|
||||
}
|
||||
|
||||
public @Nullable RawType getCoverArt() {
|
||||
byte[] localCoverArt = coverArt;
|
||||
return localCoverArt == null ? null : new RawType(localCoverArt, "image/jpeg");
|
||||
}
|
||||
|
||||
public OnOffType getRandom() {
|
||||
return OnOffType.from(random);
|
||||
}
|
||||
|
||||
public void setRandom(boolean val) {
|
||||
if (val != this.random) {
|
||||
this.random = val;
|
||||
this.randomDirty = true;
|
||||
} else {
|
||||
this.randomDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public OnOffType getRepeat() {
|
||||
return OnOffType.from(repeat);
|
||||
}
|
||||
|
||||
public void setRepeat(boolean val) {
|
||||
if (val != this.repeat) {
|
||||
this.repeat = val;
|
||||
this.repeatDirty = true;
|
||||
} else {
|
||||
this.repeatDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public StringType getPosition() {
|
||||
return new StringType(position);
|
||||
}
|
||||
|
||||
public boolean isPositionDirty() {
|
||||
return positionDirty;
|
||||
}
|
||||
|
||||
public boolean isStateDirty() {
|
||||
return stateDirty;
|
||||
}
|
||||
|
||||
public boolean isTitleDirty() {
|
||||
return titleDirty;
|
||||
}
|
||||
|
||||
public boolean isAlbumDirty() {
|
||||
return albumDirty;
|
||||
}
|
||||
|
||||
public boolean isArtistDirty() {
|
||||
return artistDirty;
|
||||
}
|
||||
|
||||
public boolean isVolumeDirty() {
|
||||
return volumeDirty;
|
||||
}
|
||||
|
||||
public boolean isTrackTypeDirty() {
|
||||
return trackTypeDirty;
|
||||
}
|
||||
|
||||
public boolean isCoverArtDirty() {
|
||||
return coverArtDirty;
|
||||
}
|
||||
|
||||
public boolean isRandomDirty() {
|
||||
return randomDirty;
|
||||
}
|
||||
|
||||
public boolean isRepeatDirty() {
|
||||
return repeatDirty;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal.mapping;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioEvents {
|
||||
|
||||
/**
|
||||
* Pushes the current state of Volumio2. For example
|
||||
* track, artist, title, volume, ...
|
||||
*
|
||||
*/
|
||||
public static final String PUSH_STATE = "pushState";
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.volumio.internal.mapping;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Patrick Sernetz - Initial Contribution
|
||||
* @author Michael Loercher - Adaption for openHAB 3
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolumioServiceTypes {
|
||||
|
||||
public static final String WEBRADIO = "webradio";
|
||||
|
||||
public static final String SPOTIFY = "spotify";
|
||||
|
||||
public static final String MPD = "mpd";
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="volumio" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>Volumio Binding</name>
|
||||
<description>This is the binding for Volumio devices.</description>
|
||||
|
||||
</addon:addon>
|
@ -0,0 +1,60 @@
|
||||
# add-on
|
||||
|
||||
addon.volumio.name = Volumio Binding
|
||||
addon.volumio.description = This is the binding for Volumio devices.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.volumio.player.label = Volumio Binding Thing
|
||||
thing-type.volumio.player.description = A Volumio Instance
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.volumio.player.hostname.label = Hostname
|
||||
thing-type.config.volumio.player.hostname.description = The hostname of your Volumio device
|
||||
thing-type.config.volumio.player.port.label = Port
|
||||
thing-type.config.volumio.player.port.description = The port of your Volumio device (default is 3000)
|
||||
thing-type.config.volumio.player.protocol.label = Protocol
|
||||
thing-type.config.volumio.player.protocol.description = The protocol of your Volumio device (default is http)
|
||||
thing-type.config.volumio.player.protocol.option.http = http
|
||||
thing-type.config.volumio.player.protocol.option.https = https
|
||||
thing-type.config.volumio.player.timeout.label = Timeout
|
||||
thing-type.config.volumio.player.timeout.description = Connection-Timeout in ms
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.volumio.album-art.label = Cover Art
|
||||
channel-type.volumio.album-art.description = Cover Art for the currently played track
|
||||
channel-type.volumio.album.label = Current Album
|
||||
channel-type.volumio.album.description = Name of the album currently playing
|
||||
channel-type.volumio.artist.label = Current Artist
|
||||
channel-type.volumio.artist.description = Name of the artist currently playing
|
||||
channel-type.volumio.clear-queue.label = Clear Queue
|
||||
channel-type.volumio.clear-queue.description = Clear the current queue
|
||||
channel-type.volumio.play-file.label = Play File
|
||||
channel-type.volumio.play-file.description = Play a file, located on your Volumio device at the given absolute path, e.g. "mnt/INTERNAL/song.mp3"
|
||||
channel-type.volumio.play-playlist.label = Play Playlist
|
||||
channel-type.volumio.play-playlist.description = Playback a playlist identified by its name
|
||||
channel-type.volumio.play-radiostream.label = Play Radio Stream
|
||||
channel-type.volumio.play-radiostream.description = Play the given radio stream
|
||||
channel-type.volumio.play-random.label = Random
|
||||
channel-type.volumio.play-random.description = Activate random mode
|
||||
channel-type.volumio.play-repeat.label = Repeat
|
||||
channel-type.volumio.play-repeat.description = Activate repeat mode
|
||||
channel-type.volumio.play-uri.label = Play URI
|
||||
channel-type.volumio.play-uri.description = Play the stream at given URI
|
||||
channel-type.volumio.player.label = State
|
||||
channel-type.volumio.player.description = The State channel contains state of the Volumio Player
|
||||
channel-type.volumio.stop-command.label = Stop
|
||||
channel-type.volumio.stop-command.description = Sends a Stop Command to Volumio. This allows to stop the player. Use "stop" as string command.
|
||||
channel-type.volumio.stop-command.state.option.stop = Stop
|
||||
channel-type.volumio.system-command.label = Send System Command
|
||||
channel-type.volumio.system-command.description = Sends a system command to Volumio. This allows to shutdown/reboot Volumio
|
||||
channel-type.volumio.system-command.state.option.shutdown = Shutdown
|
||||
channel-type.volumio.system-command.state.option.reboot = Reboot
|
||||
channel-type.volumio.title.label = Current Title
|
||||
channel-type.volumio.title.description = Title of the song currently playing
|
||||
channel-type.volumio.track-type.label = Track Type
|
||||
channel-type.volumio.track-type.description = Tracktype of the currently played track
|
||||
channel-type.volumio.volume.label = Volume
|
||||
channel-type.volumio.volume.description = Set or get the master volume
|
@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="volumio"
|
||||
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="player">
|
||||
<label>Volumio Binding Thing</label>
|
||||
<description>A Volumio Instance</description>
|
||||
|
||||
<channels>
|
||||
<channel id="title" typeId="title"/>
|
||||
<channel id="artist" typeId="artist"/>
|
||||
<channel id="album" typeId="album"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="player" typeId="player"/>
|
||||
<channel id="track-type" typeId="track-type"/>
|
||||
<channel id="play-radiostream" typeId="play-radiostream"/>
|
||||
<channel id="play-playlist" typeId="play-playlist"/>
|
||||
<channel id="clear-queue" typeId="clear-queue"/>
|
||||
<channel id="play-uri" typeId="play-uri"/>
|
||||
<channel id="play-file" typeId="play-file"/>
|
||||
<channel id="random" typeId="play-random"/>
|
||||
<channel id="repeat" typeId="play-repeat"/>
|
||||
<channel id="system-command" typeId="system-command"/>
|
||||
<channel id="stop-command" typeId="stop-command"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="hostname" type="text" required="true">
|
||||
<label>Hostname</label>
|
||||
<description>The hostname of your Volumio device</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true">
|
||||
<label>Port</label>
|
||||
<description>The port of your Volumio device (default is 3000)</description>
|
||||
<default>3000</default>
|
||||
</parameter>
|
||||
<parameter name="protocol" type="text" required="true">
|
||||
<label>Protocol</label>
|
||||
<description>The protocol of your Volumio device (default is http)</description>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="http">http</option>
|
||||
<option value="https">https</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="timeout" type="integer" required="true">
|
||||
<label>Timeout</label>
|
||||
<description>Connection-Timeout in ms</description>
|
||||
<default>5000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="system-command" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Send System Command</label>
|
||||
<description>Sends a system command to Volumio. This allows to shutdown/reboot Volumio</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="shutdown">Shutdown</option>
|
||||
<option value="reboot">Reboot</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="stop-command" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Stop</label>
|
||||
<description>Sends a Stop Command to Volumio. This allows to stop the player. Use "stop" as string command.
|
||||
</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="stop">Stop</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Title</label>
|
||||
<description>Title of the song currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="artist">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Artist</label>
|
||||
<description>Name of the artist currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album">
|
||||
<item-type>String</item-type>
|
||||
<label>Current Album</label>
|
||||
<description>Name of the album currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="volume">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Set or get the master volume</description>
|
||||
<category>SoundVolume</category>
|
||||
<state max="100" min="0" step="10"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="player">
|
||||
<item-type>Player</item-type>
|
||||
<label>State</label>
|
||||
<description>The State channel contains state of the Volumio Player</description>
|
||||
<category>Player</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album-art" advanced="true">
|
||||
<item-type>Image</item-type>
|
||||
<label>Cover Art</label>
|
||||
<description>Cover Art for the currently played track</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track-type" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Track Type</label>
|
||||
<description>Tracktype of the currently played track</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-radiostream" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Radio Stream</label>
|
||||
<description>Play the given radio stream</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-playlist" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Playlist</label>
|
||||
<description>Playback a playlist identified by its name</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="clear-queue" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Clear Queue</label>
|
||||
<description>Clear the current queue</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-random" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Random</label>
|
||||
<description>Activate random mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-repeat" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Repeat</label>
|
||||
<description>Activate repeat mode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-uri" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Play URI</label>
|
||||
<description>Play the stream at given URI</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="play-file" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Play File</label>
|
||||
<description>Play a file, located on your Volumio device at the given absolute path, e.g.
|
||||
"mnt/INTERNAL/song.mp3"
|
||||
</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -403,6 +403,7 @@
|
||||
<module>org.openhab.binding.vitotronic</module>
|
||||
<module>org.openhab.binding.vizio</module>
|
||||
<module>org.openhab.binding.volvooncall</module>
|
||||
<module>org.openhab.binding.volumio</module>
|
||||
<module>org.openhab.binding.warmup</module>
|
||||
<module>org.openhab.binding.weathercompany</module>
|
||||
<module>org.openhab.binding.weatherunderground</module>
|
||||
|
Loading…
Reference in New Issue
Block a user