added migrated 2.x add-ons

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,106 @@
# MPD Binding
[Music Player Daemon (MPD)](http://www.musicpd.org/) is a flexible, powerful, server-side application for playing music. Through plugins and libraries it can play a variety of sound files while being controlled by its network protocol.
With the openHAB MPD binding you can control Music Player Daemons.
## Supported Things
This binding supports one ThingType: mpd
## Discovery
If zeroconf is enabled in the Music Player Daemon, it is discovered. Each Music Player daemon requires a unique zeroconf_name for correct discovery.
## Thing Configuration
The ThingType mpd requires the following configuration parameters:
| Parameter Label | Parameter ID | Description | Required |
|-----------------|--------------|--------------------------------------------------------------------------|----------|
| IP Address | ipAddress | Host name or IP address of the Music Player Daemon | yes |
| Port | port | Port number on which the Music Player Daemon is listening. Default: 6600 | yes |
| Password | password | Password to access the Music Player Daemon | no |
## Channels
The following channels are currently available:
| Channel Type ID | Item Type | Description |
|-----------------|-----------|---------------------------|
| control | Player | Start/Pause/Next/Previous |
| volume | Dimmer | Volume in percent |
| stop | Switch | Stop playback |
| currentalbum | String | Current album |
| currentartist | String | Current artist |
| currentname | String | Current name |
| currentsong | Number | Current song |
| currentsongid | Number | Current song id |
| currenttitle | String | Current title |
| currenttrack | Number | Current track |
## Full Example
#### Thing
```
mpd:mpd:music [ ipAddress="192.168.1.2", port=6600 ]
```
#### Items
```
Switch morning_music "Morning music"
Player mpd_music_player "Player" { channel = "mpd:mpd:music:control" }
Dimmer mpd_music_volume "Volume [%d %%]" { channel = "mpd:mpd:music:volume" }
Switch mpd_music_stop "Stop" { channel = "mpd:mpd:music:stop" }
String mpd_music_album "Album [%s]" { channel = "mpd:mpd:music:currentalbum" }
String mpd_music_artist "Artist [%s]" { channel = "mpd:mpd:music:currentartist" }
String mpd_music_name "Name [%s]" { channel = "mpd:mpd:music:currentname" }
Number mpd_music_song "Song [%d]" { channel = "mpd:mpd:music:currentsong" }
Number mpd_music_song_id "Song Id [%d]" { channel = "mpd:mpd:music:currentsongid" }
String mpd_music_title "Title [%s]" { channel = "mpd:mpd:music:currenttitle" }
Number mpd_music_track "Track [%d]" { channel = "mpd:mpd:music:currenttrack" }
```
#### Sitemap
```
Frame label="Music" {
Default item=mpd_music_player
Slider item=mpd_music_volume
Switch item=mpd_music_stop
Text item=mpd_music_album
Text item=mpd_music_artist
Text item=mpd_music_name
Text item=mpd_music_song
Text item=mpd_music_song_id
Text item=mpd_music_title
Text item=mpd_music_track
}
```
#### Rule
```
rule "turn on morning music"
when
Item morning_music changed to ON
then
val actions = getActions("mpd","mpd:mpd:music")
if(actions === null) {
logWarn("myLog", "actions is null")
return
}
actions.sendCommand("clear")
actions.sendCommand("load", "MorningMusic");
actions.sendCommand("shuffle");
actions.sendCommand("play");
end
```

View File

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

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.mpd-${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-mpd" description="MPD Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mpd/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link MPDBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDBindingConstants {
private static final String BINDING_ID = "mpd";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_MPD = new ThingTypeUID(BINDING_ID, "mpd");
// List of all Channel ids
public static final String CHANNEL_CONTROL = "control";
public static final String CHANNEL_CURRENT_ALBUM = "currentalbum";
public static final String CHANNEL_CURRENT_ARTIST = "currentartist";
public static final String CHANNEL_CURRENT_NAME = "currentname";
public static final String CHANNEL_CURRENT_SONG = "currentsong";
public static final String CHANNEL_CURRENT_SONG_ID = "currentsongid";
public static final String CHANNEL_CURRENT_TITLE = "currenttitle";
public static final String CHANNEL_CURRENT_TRACK = "currenttrack";
public static final String CHANNEL_STOP = "stop";
public static final String CHANNEL_VOLUME = "volume";
// Config Parameters
public static final String PARAMETER_IPADDRESS = "ipAddress";
public static final String PARAMETER_PORT = "port";
public static final String UNIQUE_ID = "uniqueId";
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MPDConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDConfiguration {
private String ipAddress = "";
private Integer port = 0;
private String password = "";
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MPDException} class is used for any exception thrown by the binding
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDException extends Exception {
private static final long serialVersionUID = 1L;
public MPDException() {
}
public MPDException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal;
import static org.openhab.binding.mpd.internal.MPDBindingConstants.THING_TYPE_MPD;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.handler.MPDHandler;
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 MPDHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.mpd", service = ThingHandlerFactory.class)
public class MPDHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_MPD);
@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 (THING_TYPE_MPD.equals(thingTypeUID)) {
return new MPDHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.action;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link IMPDActions} interface defines rule actions for sending commands to a Music Player Daemon
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public interface IMPDActions {
public void sendCommand(@Nullable String command, @Nullable String parameter);
public void sendCommand(@Nullable String command);
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.action;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.handler.MPDHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link @MPDActions} defines rule actions for the Music Player Daemon binding.
*
* @author Stefan Röllin - Initial contribution
*/
@ThingActionsScope(name = "mpd")
@NonNullByDefault
public class MPDActions implements ThingActions, IMPDActions {
private final Logger logger = LoggerFactory.getLogger(MPDActions.class);
private @Nullable MPDHandler handler = null;
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof MPDHandler) {
this.handler = (MPDHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
@Override
@RuleAction(label = "MPD : Send command", description = "Send a command to the Music Player Daemon.")
public void sendCommand(@ActionInput(name = "command") @Nullable String command,
@ActionInput(name = "parameter") @Nullable String parameter) {
logger.debug("sendCommand called with {}", command);
MPDHandler handler = this.handler;
if (handler != null) {
handler.sendCommand(command, parameter);
} else {
logger.warn("MPD Action service ThingHandler is null!");
}
}
@Override
@RuleAction(label = "MPD : Send command", description = "Send a command to the Music Player Daemon.")
public void sendCommand(@ActionInput(name = "command") @Nullable String command) {
logger.debug("sendCommand called with {}", command);
MPDHandler handler = this.handler;
if (handler != null) {
handler.sendCommand(command);
} else {
logger.warn("MPD Action service ThingHandler is null!");
}
}
private static IMPDActions invokeMethodOf(@Nullable ThingActions actions) {
if (actions == null) {
throw new IllegalArgumentException("actions cannot be null");
}
if (actions.getClass().getName().equals(MPDActions.class.getName())) {
if (actions instanceof IMPDActions) {
return (IMPDActions) actions;
} else {
return (IMPDActions) Proxy.newProxyInstance(IMPDActions.class.getClassLoader(),
new Class[] { IMPDActions.class }, (Object proxy, Method method, Object[] args) -> {
Method m = actions.getClass().getDeclaredMethod(method.getName(),
method.getParameterTypes());
return m.invoke(actions, args);
});
}
}
throw new IllegalArgumentException("Actions is not an instance of MPDActions");
}
public static void sendCommand(@Nullable ThingActions actions, @Nullable String command,
@Nullable String parameter) {
invokeMethodOf(actions).sendCommand(command, parameter);
}
public static void sendCommand(@Nullable ThingActions actions, @Nullable String command) {
invokeMethodOf(actions).sendCommand(command);
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.discovery;
import java.net.Inet4Address;
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.mpd.internal.MPDBindingConstants;
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;
/**
* Implementation of {@link MDNSDiscoveryParticipant} that will discover Music Player Daemons.
*
* @author Stefan Röllin - Initial contribution
*
*/
@NonNullByDefault
@Component(immediate = true)
public class MPDDiscoveryParticipant implements MDNSDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(MPDDiscoveryParticipant.class);
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(MPDBindingConstants.THING_TYPE_MPD);
}
@Override
public String getServiceType() {
return "_mpd._tcp.local.";
}
@Override
@Nullable
public DiscoveryResult createResult(ServiceInfo service) {
ThingUID uid = getThingUID(service);
String host = getHostAddress(service);
int port = service.getPort();
logger.debug("Music Player Daemon found on host {} port {}", host, port);
if (uid == null || host == null || host.isEmpty()) {
return null;
}
String uniquePropVal = String.format("%s-%d", host, port);
final Map<String, Object> properties = new HashMap<>(3);
properties.put(MPDBindingConstants.PARAMETER_IPADDRESS, host);
properties.put(MPDBindingConstants.PARAMETER_PORT, port);
properties.put(MPDBindingConstants.UNIQUE_ID, uniquePropVal);
String name = service.getName();
final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(name).withProperties(properties)
.withRepresentationProperty(MPDBindingConstants.UNIQUE_ID).build();
return result;
}
@Nullable
private String getHostAddress(ServiceInfo service) {
if (service.getInet4Addresses() != null) {
for (Inet4Address addr : service.getInet4Addresses()) {
if (addr != null) {
return addr.getHostAddress();
}
}
}
return null;
}
@Override
@Nullable
public ThingUID getThingUID(ServiceInfo service) {
if (getServiceType().equals(service.getType())) {
String name = getUIDName(service.getName());
if (!name.isEmpty()) {
return new ThingUID(MPDBindingConstants.THING_TYPE_MPD, name);
}
}
return null;
}
private String getUIDName(@Nullable String serviceName) {
if (serviceName == null) {
return "";
}
return serviceName.replaceAll("[^A-Za-z0-9_]", "_").replaceAll("_+", "_");
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.handler;
import java.util.EventListener;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.protocol.MPDConnection;
import org.openhab.binding.mpd.internal.protocol.MPDSong;
import org.openhab.binding.mpd.internal.protocol.MPDStatus;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Interface which has to be implemented by a class in order to get
* updates from a {@link MPDConnection}
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public interface MPDEventListener extends EventListener {
void updateMPDSong(MPDSong song);
void updateMPDStatus(MPDStatus status);
void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
}

View File

@@ -0,0 +1,293 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.handler;
import static org.openhab.binding.mpd.internal.MPDBindingConstants.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.MPDBindingConstants;
import org.openhab.binding.mpd.internal.MPDConfiguration;
import org.openhab.binding.mpd.internal.action.MPDActions;
import org.openhab.binding.mpd.internal.protocol.MPDConnection;
import org.openhab.binding.mpd.internal.protocol.MPDSong;
import org.openhab.binding.mpd.internal.protocol.MPDStatus;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.NextPreviousType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.PlayPauseType;
import org.openhab.core.library.types.StringType;
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.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MPDHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDHandler extends BaseThingHandler implements MPDEventListener {
private final Logger logger = LoggerFactory.getLogger(MPDHandler.class);
private Map<String, @Nullable State> stateMap = Collections.synchronizedMap(new HashMap<String, @Nullable State>());
private final MPDConnection connection;
private int volume = 0;
private @Nullable ScheduledFuture<?> futureUpdateStatus;
private @Nullable ScheduledFuture<?> futureUpdateCurrentSong;
public MPDHandler(Thing thing) {
super(thing);
connection = new MPDConnection(this);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
handleCommandRefresh(channelUID.getId());
} else {
handlePlayerCommand(channelUID.getId(), command);
}
}
@Override
public void initialize() {
MPDConfiguration config = getConfigAs(MPDConfiguration.class);
String uniquePropVal = String.format("%s-%d", config.getIpAddress(), config.getPort());
updateProperty(MPDBindingConstants.UNIQUE_ID, uniquePropVal);
updateStatus(ThingStatus.UNKNOWN);
connection.start(config.getIpAddress(), config.getPort(), config.getPassword(),
"OH-binding-" + getThing().getUID().getAsString());
}
@Override
public void dispose() {
ScheduledFuture<?> future = this.futureUpdateStatus;
if (future != null) {
future.cancel(true);
}
future = this.futureUpdateCurrentSong;
if (future != null) {
future.cancel(true);
}
connection.dispose();
super.dispose();
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(MPDActions.class);
}
/**
* send a command to the music player daemon
*
* @param command command to send
* @param parameter parameter of command
*/
public void sendCommand(@Nullable String command, String... parameter) {
if (command != null) {
connection.sendCommand(command, parameter);
} else {
logger.warn("can't send null command");
}
}
private void handleCommandRefresh(String channelId) {
stateMap.remove(channelId);
switch (channelId) {
case CHANNEL_CONTROL:
case CHANNEL_STOP:
case CHANNEL_VOLUME:
scheduleUpdateStatus();
break;
case CHANNEL_CURRENT_ALBUM:
case CHANNEL_CURRENT_ARTIST:
case CHANNEL_CURRENT_NAME:
case CHANNEL_CURRENT_SONG:
case CHANNEL_CURRENT_SONG_ID:
case CHANNEL_CURRENT_TITLE:
case CHANNEL_CURRENT_TRACK:
scheduleUpdateCurrentSong();
break;
}
}
private synchronized void scheduleUpdateStatus() {
logger.debug("scheduleUpdateStatus");
ScheduledFuture<?> future = this.futureUpdateStatus;
if (future == null || future.isCancelled() || future.isDone()) {
this.futureUpdateStatus = scheduler.schedule(this::doUpdateStatus, 100, TimeUnit.MILLISECONDS);
}
}
private void doUpdateStatus() {
connection.updateStatus();
}
private synchronized void scheduleUpdateCurrentSong() {
logger.debug("scheduleUpdateCurrentSong");
ScheduledFuture<?> future = this.futureUpdateCurrentSong;
if (future == null || future.isCancelled() || future.isDone()) {
this.futureUpdateCurrentSong = scheduler.schedule(this::doUpdateCurrentSong, 100, TimeUnit.MILLISECONDS);
}
}
private void doUpdateCurrentSong() {
connection.updateCurrentSong();
}
private void handlePlayerCommand(String channelId, Command command) {
switch (channelId) {
case CHANNEL_CONTROL:
handleCommandControl(command);
break;
case CHANNEL_STOP:
handleCommandStop(command);
break;
case CHANNEL_VOLUME:
handleCommandVolume(command);
break;
}
}
private void handleCommandControl(Command command) {
if (command instanceof PlayPauseType) {
if (command == PlayPauseType.PLAY) {
connection.play();
} else if (command == PlayPauseType.PAUSE) {
connection.pause();
}
} else if (command instanceof NextPreviousType) {
if (command == NextPreviousType.NEXT) {
connection.playNext();
} else if (command == NextPreviousType.PREVIOUS) {
connection.playPrevious();
}
} else {
// Rewind and Fast Forward are currently not implemented by the binding
logger.debug("Control command {} is not supported", command);
}
}
private void handleCommandStop(Command command) {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
connection.stop();
} else if (command == OnOffType.OFF) {
connection.play();
}
} else {
logger.debug("Stop Command {} is not supported", command);
return;
}
}
private void handleCommandVolume(Command command) {
int newValue = 0;
if (command instanceof IncreaseDecreaseType) {
if (command == IncreaseDecreaseType.INCREASE) {
newValue = Math.min(100, volume + 1);
} else if (command == IncreaseDecreaseType.DECREASE) {
newValue = Math.max(0, volume - 1);
}
} else if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
newValue = 100;
} else if (command == OnOffType.OFF) {
newValue = 0;
}
} else if (command instanceof DecimalType) {
newValue = ((DecimalType) command).intValue();
} else if (command instanceof PercentType) {
newValue = ((PercentType) command).intValue();
} else {
logger.debug("Command {} is not supported to change volume", command);
return;
}
connection.setVolume(newValue);
}
private void updateChannel(String channelID, State state) {
State previousState = stateMap.put(channelID, state);
if (previousState == null || !previousState.equals(state)) {
updateState(channelID, state);
}
}
@Override
public void updateMPDStatus(MPDStatus status) {
volume = status.getVolume();
updateChannel(CHANNEL_VOLUME, new PercentType(status.getVolume()));
State newControlState = UnDefType.UNDEF;
switch (status.getState()) {
case PLAY:
newControlState = PlayPauseType.PLAY;
break;
case STOP:
case PAUSE:
newControlState = PlayPauseType.PAUSE;
break;
}
updateChannel(CHANNEL_CONTROL, newControlState);
State newStopState = OnOffType.OFF;
if (status.getState() == MPDStatus.State.STOP) {
newStopState = OnOffType.ON;
}
updateChannel(CHANNEL_STOP, newStopState);
}
@Override
public void updateMPDSong(MPDSong song) {
updateChannel(CHANNEL_CURRENT_ALBUM, new StringType(song.getAlbum()));
updateChannel(CHANNEL_CURRENT_ARTIST, new StringType(song.getArtist()));
updateChannel(CHANNEL_CURRENT_NAME, new StringType(song.getName()));
updateChannel(CHANNEL_CURRENT_SONG, new DecimalType(song.getSong()));
updateChannel(CHANNEL_CURRENT_SONG_ID, new DecimalType(song.getSongId()));
updateChannel(CHANNEL_CURRENT_TITLE, new StringType(song.getTitle()));
updateChannel(CHANNEL_CURRENT_TRACK, new DecimalType(song.getTrack()));
}
@Override
public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
updateStatus(status, statusDetail, description);
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class for encapsulating an MPD command
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDCommand {
private final String command;
private final List<String> parameters = new ArrayList<>();
/**
* Create an MPD command without parameters
*
* @param command the command to send
*/
public MPDCommand(String command) {
this.command = command;
}
/**
* Create an MPD command with one Integer parameter
*
* @param command the command to send
* @param value parameter of the command
*/
public MPDCommand(String command, Integer value) {
this.command = command;
parameters.add(Integer.toString(value));
}
/**
* Create an MPD command with parameters
*
* @param command the command to send
* @param parameters the parameters of the command to send
*/
public MPDCommand(String command, String... parameters) {
this.command = command;
Collections.addAll(this.parameters, Arrays.copyOf(parameters, parameters.length));
}
/**
* Returns the command.
*
* @return the command
*/
public String getCommand() {
return command;
}
/**
* Returns the command as one line, including the parameters
*
* @return the command and parameters
*/
public String asLine() {
StringBuilder builder = new StringBuilder(command);
for (String param : parameters) {
builder.append(" ");
builder.append("\"");
builder.append(param.replaceAll("\"", "\\\\\"").replaceAll("'", "\\\\'"));
builder.append("\"");
}
return builder.toString();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(command);
for (String param : parameters) {
builder.append(" ");
builder.append("\"");
if ("password".equals(command)) {
builder.append(param.replaceAll(".", "."));
} else {
builder.append(param.replaceAll("\"", "\\\\\"").replaceAll("'", "\\\\'"));
}
builder.append("\"");
}
return builder.toString();
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.handler.MPDEventListener;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for communicating with the music player daemon through a IP connection
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDConnection implements MPDResponseListener {
private static final int DISPOSE_TIMEOUT_MS = 1000;
private final Logger logger = LoggerFactory.getLogger(MPDConnection.class);
private final MPDEventListener listener;
private @Nullable MPDConnectionThread connectionThread = null;
/**
* Constructor
*
* @param address the IP address of the music player daemon
* @param port the TCP port to be used
* @param password the password to connect to the music player daemon
*/
public MPDConnection(MPDEventListener listener) {
this.listener = listener;
}
/**
* start the connection
*
* @param address the IP address of the music player daemon
* @param port the TCP port to be used
* @param password the password to connect to the music player daemon
* @param threadName the name of the thread
*/
public void start(String address, Integer port, String password, String threadName) {
if (connectionThread == null) {
final MPDConnectionThread connectionThread = new MPDConnectionThread(this, address, port, password);
connectionThread.setName(threadName);
connectionThread.start();
this.connectionThread = connectionThread;
}
}
/**
* dispose the connection
*/
public void dispose() {
final MPDConnectionThread connectionThread = this.connectionThread;
if (connectionThread != null) {
connectionThread.dispose();
connectionThread.interrupt();
try {
connectionThread.join(DISPOSE_TIMEOUT_MS);
} catch (InterruptedException ignore) {
}
this.connectionThread = null;
}
}
/**
* send a command to the music player daemon
*
* @param command command to send
* @param parameter parameter of command
*/
public void sendCommand(String command, String... parameter) {
addCommand(new MPDCommand(command, parameter));
}
/**
* play
*/
public void play() {
sendCommand("play");
}
/**
* pause the music player daemon
*/
public void pause() {
addCommand(new MPDCommand("pause", 1));
}
/**
* play next track
*/
public void playNext() {
sendCommand("next");
}
/**
* play previous track
*/
public void playPrevious() {
sendCommand("previous");
}
/**
* stop the music player daemon
*/
public void stop() {
sendCommand("stop");
}
/**
* update status
*/
public void updateStatus() {
sendCommand("status");
}
/**
* update information regarding current song
*/
public void updateCurrentSong() {
sendCommand("currentsong");
}
/**
* set volume
*
* @param volume set new volume
*/
public void setVolume(int volume) {
addCommand(new MPDCommand("setvol", volume));
}
private void addCommand(MPDCommand command) {
MPDConnectionThread connectionThread = this.connectionThread;
if (connectionThread != null) {
connectionThread.addCommand(command);
} else {
logger.debug("could not add command {} since thing offline", command.getCommand());
}
}
@Override
public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String cause) {
listener.updateThingStatus(status, statusDetail, cause);
}
@Override
public void onResponse(MPDResponse response) {
switch (response.getCommand()) {
case "idle":
handleResponseIdle(response);
break;
case "status":
handleResponseStatus(response);
break;
case "currentsong":
handleResponseCurrentSong(response);
break;
default:
break;
}
}
private void handleResponseCurrentSong(MPDResponse response) {
MPDSong song = new MPDSong(response);
listener.updateMPDSong(song);
}
private void handleResponseIdle(MPDResponse response) {
boolean updateStatus = false;
boolean updateCurrentSong = false;
for (String line : response.getLines()) {
if (line.startsWith("changed:")) {
line = line.substring(8).trim();
switch (line) {
case "player":
updateStatus = true;
break;
case "mixer":
updateStatus = true;
break;
case "playlist":
updateCurrentSong = true;
break;
}
}
}
if (updateStatus) {
updateStatus();
}
if (updateCurrentSong) {
updateCurrentSong();
}
}
private void handleResponseStatus(MPDResponse response) {
MPDStatus song = new MPDStatus(response);
listener.updateMPDStatus(song);
}
}

View File

@@ -0,0 +1,299 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mpd.internal.MPDException;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for communicating with the music player daemon through a IP connection
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDConnectionThread extends Thread {
private static final int RECONNECTION_TIMEOUT_SEC = 60;
private final Logger logger = LoggerFactory.getLogger(MPDConnectionThread.class);
private final MPDResponseListener listener;
private final String address;
private final Integer port;
private final String password;
private @Nullable Socket socket = null;
private @Nullable InputStreamReader inputStreamReader = null;
private @Nullable BufferedReader reader = null;
private final List<MPDCommand> pendingCommands = new ArrayList<>();
private AtomicBoolean isInIdle = new AtomicBoolean(false);
private AtomicBoolean disposed = new AtomicBoolean(false);
public MPDConnectionThread(MPDResponseListener listener, String address, Integer port, String password) {
this.listener = listener;
this.address = address;
this.port = port;
this.password = password;
setDaemon(true);
}
@Override
public void run() {
try {
while (!disposed.get()) {
try {
synchronized (pendingCommands) {
pendingCommands.clear();
pendingCommands.add(new MPDCommand("status"));
pendingCommands.add(new MPDCommand("currentsong"));
}
establishConnection();
updateThingStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
processPendingCommands();
} catch (UnknownHostException e) {
updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Unknown host " + address);
} catch (IOException e) {
updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (MPDException e) {
updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
isInIdle.set(false);
closeSocket();
if (!disposed.get()) {
sleep(RECONNECTION_TIMEOUT_SEC * 1000);
}
}
} catch (InterruptedException ignore) {
}
}
/**
* dispose the connection
*/
public void dispose() {
disposed.set(true);
Socket socket = this.socket;
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
}
this.socket = null;
}
}
/**
* add a command to the pending commands queue
*
* @param command command to add
*/
public void addCommand(MPDCommand command) {
insertCommand(command, -1);
}
private void insertCommand(MPDCommand command, int position) {
logger.debug("insert command '{}' at position {}", command.getCommand(), position);
int index = position;
synchronized (pendingCommands) {
if (index < 0) {
index = pendingCommands.size();
}
pendingCommands.add(index, command);
sendNoIdleIfInIdle();
}
}
private void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
if (!disposed.get()) {
listener.updateThingStatus(status, statusDetail, description);
}
}
private void sendNoIdleIfInIdle() {
if (isInIdle.compareAndSet(true, false)) {
try {
sendCommand(new MPDCommand("noidle"));
} catch (IOException e) {
logger.debug("sendCommand(noidle) failed", e);
}
}
}
private void establishConnection() throws UnknownHostException, IOException, MPDException {
openSocket();
MPDCommand currentCommand = new MPDCommand("connect");
MPDResponse response = readResponse(currentCommand);
if (!response.isOk()) {
throw new MPDException("Failed to connect to " + this.address + ":" + this.port);
}
if (!password.isEmpty()) {
currentCommand = new MPDCommand("password", password);
sendCommand(currentCommand);
response = readResponse(currentCommand);
if (!response.isOk()) {
throw new MPDException("Could not authenticate, please validate your password");
}
}
}
private void openSocket() throws UnknownHostException, IOException, MPDException {
logger.debug("opening connection to {} port {}", address, port);
if (address.isEmpty()) {
throw new MPDException("The parameter 'ipAddress' is missing.");
}
if (port < 1 || port > 65335) {
throw new MPDException("The parameter 'port' has an invalid value.");
}
Socket socket = new Socket(address, port);
inputStreamReader = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
reader = new BufferedReader(inputStreamReader);
this.socket = socket;
}
private void processPendingCommands() throws IOException, MPDException {
MPDCommand currentCommand;
while (!disposed.get()) {
synchronized (pendingCommands) {
if (!pendingCommands.isEmpty()) {
currentCommand = pendingCommands.remove(0);
} else {
currentCommand = new MPDCommand("idle");
}
sendCommand(currentCommand);
if ("idle".equals(currentCommand.getCommand())) {
isInIdle.set(true);
}
}
MPDResponse response = readResponse(currentCommand);
if (!response.isOk()) {
insertCommand(new MPDCommand("clearerror"), 0);
}
listener.onResponse(response);
}
}
private void closeSocket() {
logger.debug("Closing socket");
BufferedReader reader = this.reader;
if (reader != null) {
try {
reader.close();
} catch (IOException ignore) {
}
this.reader = null;
}
InputStreamReader inputStreamReader = this.inputStreamReader;
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException ignore) {
}
this.inputStreamReader = null;
}
Socket socket = this.socket;
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
}
this.socket = null;
}
}
private void sendCommand(MPDCommand command) throws IOException {
logger.trace("send command '{}'", command);
final Socket socket = this.socket;
if (socket != null) {
String line = command.asLine();
socket.getOutputStream().write(line.getBytes(StandardCharsets.UTF_8));
socket.getOutputStream().write('\n');
} else {
throw new IOException("Connection closed unexpectedly.");
}
}
private MPDResponse readResponse(MPDCommand command) throws IOException, MPDException {
logger.trace("read response for command '{}'", command.getCommand());
MPDResponse response = new MPDResponse(command.getCommand());
boolean done = false;
final BufferedReader reader = this.reader;
if (reader != null) {
while (!done) {
String line = reader.readLine();
logger.trace("received line '{}'", line);
if (line != null) {
if (line.startsWith("ACK [4")) {
logger.warn("command '{}' failed with permission error '{}'", command, line);
isInIdle.set(false);
throw new MPDException(
"Please validate your password and/or your permissions on the Music Player Daemon.");
} else if (line.startsWith("ACK")) {
logger.warn("command '{}' failed with '{}'", command, line);
response.setFailed();
done = true;
} else if (line.startsWith("OK")) {
done = true;
} else {
response.addLine(line.trim());
}
} else {
isInIdle.set(false);
throw new IOException("Communication failed unexpectedly.");
}
}
} else {
isInIdle.set(false);
throw new IOException("Connection closed unexpectedly.");
}
isInIdle.set(false);
return response;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class for encapsulating an MPD response
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDResponse {
private final String command;
private final List<String> lines = new ArrayList<>();
private boolean failed = false;
public MPDResponse(String command) {
this.command = command;
}
public void addLine(String line) {
lines.add(line);
}
public String getCommand() {
return command;
}
public List<String> getLines() {
return lines;
}
public boolean isOk() {
return !failed;
}
public void setFailed() {
failed = true;
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Listener for responses from Music Player Daemon.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public interface MPDResponseListener {
void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
void onResponse(MPDResponse response);
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class for parsing a response from a Music Player Daemon.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDResponseParser {
static Map<String, String> responseToMap(MPDResponse response) {
Map<String, String> map = new HashMap<String, String>();
for (String line : response.getLines()) {
int offset = line.indexOf(':');
if (offset >= 0) {
String key = line.substring(0, offset);
String value = line.substring(offset + 1).trim();
map.put(key, value);
}
}
return map;
}
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for representing a song.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDSong {
private final Logger logger = LoggerFactory.getLogger(MPDSong.class);
private final String filename;
private final String album;
private final String artist;
private final String name;
private final int song;
private final int songId;
private final String title;
private final int track;
public MPDSong(MPDResponse response) {
Map<String, String> values = MPDResponseParser.responseToMap(response);
filename = values.getOrDefault("file", "");
album = values.getOrDefault("Album", "");
artist = values.getOrDefault("Artist", "");
name = values.getOrDefault("Name", "");
song = parseInteger(values.getOrDefault("Pos", "0"), 0);
songId = parseInteger(values.getOrDefault("Id", "0"), 0);
title = values.getOrDefault("Title", "");
track = parseInteger(values.getOrDefault("Track", "-1"), -1);
}
public String getFilename() {
return filename;
}
public String getAlbum() {
return album;
}
public String getArtist() {
return artist;
}
public String getName() {
return name;
}
public int getSong() {
return song;
}
public int getSongId() {
return songId;
}
public String getTitle() {
return title;
}
public int getTrack() {
return track;
}
private int parseInteger(String value, int aDefault) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
logger.debug("parseInt of {} failed", value);
}
return aDefault;
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mpd.internal.protocol;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for representing the status of a Music Player Daemon.
*
* @author Stefan Röllin - Initial contribution
*/
@NonNullByDefault
public class MPDStatus {
public enum State {
PLAY,
PAUSE,
STOP
}
private final Logger logger = LoggerFactory.getLogger(MPDStatus.class);
private final State state;
private final int volume;
public MPDStatus(MPDResponse response) {
Map<String, String> values = MPDResponseParser.responseToMap(response);
state = parseState(values.getOrDefault("state", ""));
volume = parseVolume(values.getOrDefault("volume", "0"));
}
public State getState() {
return state;
}
public int getVolume() {
return volume;
}
private State parseState(String value) {
switch (value) {
case "play":
return State.PLAY;
case "pause":
return State.PAUSE;
case "stop":
return State.STOP;
}
return State.STOP;
}
private int parseVolume(String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
logger.debug("parseVolume of {} failed", value);
}
return 0;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="mpd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>MPD Binding</name>
<description>This is the binding for the Music Player Daemon.</description>
<author>Stefan Röllin</author>
</binding:binding>

View File

@@ -0,0 +1,28 @@
binding.mpd.name = MPD Binding
binding.mpd.description = Das MPD Binding erlaubt es Music Player Daemons zu steuern.
# thing types
thing-type.mpd.mpd.label = Music Player Daemon
thing-type.mpd.mpd.description = Music Player Daemon
# thing type config description
thing-type.config.mpd.mpd.ipAddress.label = IP-Adresse
thing-type.config.mpd.mpd.ipAddress.description = Lokale IP-Adresse oder Hostname des Music Player Daemons.
thing-type.config.mpd.mpd.port.label = Port
thing-type.config.mpd.mpd.port.description = Port des Music Player Daemons.
thing-type.config.mpd.mpd.password.label = Passwort
thing-type.config.mpd.mpd.password.description = Passwort zur Authentifizierung am Music Player Daemon.
# channel types
channel-type.mpd.currentalbum.label = Album
channel-type.mpd.currentalbum.description = Zeigt das Album des aktuellen Stücks an.
channel-type.mpd.currentname.label = Aktueller Name
channel-type.mpd.currentname.description = Name des aktuellen Stücks. Entspricht nicht dem Namen. Die genaue Bedeutung dieses Tags ist nicht genau definiert. Es wird häufig von schlecht konfigurierten Internetradiosendern mit defekten Tags verwendet, um sowohl den Künstlernamen als auch den Songtitel in einem Tag zusammenzufassen.
channel-type.mpd.currentsong.label = Aktuelle Song Nummer
channel-type.mpd.currentsong.description = Nummer des aktuellen Stücks.
channel-type.mpd.currentsongid.label = Aktuelle Song Id
channel-type.mpd.currentsongid.description = Id des aktuellen Stücks.
channel-type.mpd.currenttrack.label = Track Nummer
channel-type.mpd.currenttrack.description = Zeigt die Nummer des aktuellen Tracks.
channel-type.mpd.stop.label = Stop
channel-type.mpd.stop.description = Ermöglicht das Stoppen der Wiedergabe.

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mpd" 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">
<!-- Mpd Thing Type -->
<thing-type id="mpd">
<label>Music Player Daemon</label>
<description>Music Player Daemon Binding</description>
<channels>
<channel id="control" typeId="system.media-control"/>
<channel id="volume" typeId="system.volume"/>
<channel id="currentalbum" typeId="currentalbum"/>
<channel id="currentartist" typeId="system.media-artist"/>
<channel id="currentname" typeId="currentname"/>
<channel id="currentsong" typeId="currentsong"/>
<channel id="currentsongid" typeId="currentsongid"/>
<channel id="currenttitle" typeId="system.media-title"/>
<channel id="currenttrack" typeId="currenttrack"/>
<channel id="stop" typeId="stop"/>
</channels>
<representation-property>uniqueId</representation-property>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<label>Network Address</label>
<description>The IP or host name of the Music Player Daemon.</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" required="true" min="1" max="65335">
<label>Port</label>
<description>Port for the Music Player Daemon</description>
<default>6600</default>
</parameter>
<parameter name="password" type="text" required="false">
<label>Password</label>
<description>Password to access the Music Player Daemon.</description>
<advanced>true</advanced>
<context>password</context>
</parameter>
</config-description>
</thing-type>
<!-- Channel Types -->
<channel-type id="currentalbum">
<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="currentname">
<item-type>String</item-type>
<label>Current Name</label>
<description>Name for current song. This is not the song title. The exact meaning of this tag is not well-defined. It
is often used by badly configured internet radio stations with broken tags to squeeze both the artist name and the
song title in one tag.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="currenttrack" advanced="true">
<item-type>Number</item-type>
<label>Current Track</label>
<description>The current track number.</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="currentsong" advanced="true">
<item-type>Number</item-type>
<label>Current Song</label>
<description>The current song number.</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="currentsongid" advanced="true">
<item-type>Number</item-type>
<label>Current Song Id</label>
<description>The current song id.</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="stop" advanced="true">
<item-type>Switch</item-type>
<label>Stop</label>
<description>Stop the Music Player Daemon. ON if the player is stopped.</description>
</channel-type>
</thing:thing-descriptions>