[volumio] Initial contribution (#14525)

Signed-off-by: Michael Loercher <MichaelLoercher@web.de>
This commit is contained in:
miloit 2023-07-07 12:18:46 +02:00 committed by GitHub
parent 1ce6e8775f
commit de1eebd174
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1849 additions and 0 deletions

View File

@ -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

View File

@ -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>

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,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
}
}
```

View 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>

View File

@ -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>

View File

@ -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";
}

View File

@ -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;
}
}

View File

@ -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());
}
};
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -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;
}
}

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>