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,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.kodi-${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-kodi" description="Kodi Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-http</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.kodi/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,129 @@
/**
* 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.kodi.internal;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.kodi.internal.handler.KodiHandler;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FixedLengthAudioStream;
import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This makes Kodi to serve as an {@link AudioSink}.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Paul Frank - Adapted for Kodi
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public class KodiAudioSink implements AudioSink {
private final Logger logger = LoggerFactory.getLogger(KodiAudioSink.class);
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Collections
.unmodifiableSet(Stream.of(AudioFormat.MP3, AudioFormat.WAV).collect(Collectors.toSet()));
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Collections
.unmodifiableSet(Stream.of(FixedLengthAudioStream.class, URLAudioStream.class).collect(Collectors.toSet()));
// Needed because Kodi does multiple requests for the stream
private static final int STREAM_TIMEOUT = 30;
private final KodiHandler handler;
private final AudioHTTPServer audioHTTPServer;
private final String callbackUrl;
public KodiAudioSink(KodiHandler handler, AudioHTTPServer audioHTTPServer, String callbackUrl) {
this.handler = handler;
this.audioHTTPServer = audioHTTPServer;
this.callbackUrl = callbackUrl;
}
@Override
public String getId() {
return handler.getThing().getUID().toString();
}
@Override
public String getLabel(Locale locale) {
return handler.getThing().getLabel();
}
@Override
public void process(AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (audioStream == null) {
// in case the audioStream is null, this should be interpreted as a request to end any currently playing
// stream.
logger.trace("Stop currently playing stream.");
handler.stop();
} else {
AudioFormat format = audioStream.getFormat();
if (!AudioFormat.MP3.isCompatible(format) && !AudioFormat.WAV.isCompatible(format)) {
throw new UnsupportedAudioFormatException("Currently only MP3 and WAV formats are supported.", format);
}
if (audioStream instanceof URLAudioStream) {
// it is an external URL, the speaker can access it itself and play it
String url = ((URLAudioStream) audioStream).getURL();
logger.trace("Processing audioStream URL {} of format {}.", url, format);
handler.playURI(new StringType(url));
} else if (audioStream instanceof FixedLengthAudioStream) {
if (callbackUrl != null) {
// we serve it on our own HTTP server for 30 seconds as Kodi requests the stream several times
// Form the URL for streaming the notification from the OH2 web server
String url = callbackUrl
+ audioHTTPServer.serve((FixedLengthAudioStream) audioStream, STREAM_TIMEOUT);
logger.trace("Processing audioStream URL {} of format {}.", url, format);
handler.playNotificationSoundURI(new StringType(url));
} else {
logger.warn("We do not have any callback url, so Kodi cannot play the audio stream!");
}
} else {
throw new UnsupportedAudioStreamException(
"Kodi can only handle URLAudioStream or FixedLengthAudioStreams.", audioStream.getClass());
}
}
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return SUPPORTED_AUDIO_FORMATS;
}
@Override
public Set<Class<? extends AudioStream>> getSupportedStreams() {
return SUPPORTED_AUDIO_STREAMS;
}
@Override
public PercentType getVolume() {
return handler.getVolume();
}
@Override
public void setVolume(PercentType volume) {
handler.setVolume(volume);
}
}

View File

@@ -0,0 +1,122 @@
/**
* 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.kodi.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link KodiBinding} class defines common constants, which are used across
* the whole binding.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Added channels for opening PVR TV or Radio streams
* @author Andreas Reinhardt & Christoph Weitkamp - Added channels for thumbnail and fanart
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
@NonNullByDefault
public class KodiBindingConstants {
public static final String BINDING_ID = "kodi";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_KODI = new ThingTypeUID(BINDING_ID, "kodi");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KODI);
// List of thing parameters names
public static final String HOST_PARAMETER = "ipAddress";
public static final String WS_PORT_PARAMETER = "port";
public static final String HTTP_PORT_PARAMETER = "httpPort";
public static final String HTTP_USER_PARAMETER = "httpUser";
public static final String HTTP_PASSWORD_PARAMETER = "httpPassword";
public static final String REFRESH_PARAMETER = "refreshInterval";
// List of all Channel ids
public static final String CHANNEL_MUTE = "mute";
public static final String CHANNEL_VOLUME = "volume";
public static final String CHANNEL_STOP = "stop";
public static final String CHANNEL_CONTROL = "control";
public static final String CHANNEL_PLAYURI = "playuri";
public static final String CHANNEL_PLAYFAVORITE = "playfavorite";
public static final String CHANNEL_PVR_OPEN_TV = "pvr-open-tv";
public static final String CHANNEL_PVR_OPEN_RADIO = "pvr-open-radio";
public static final String CHANNEL_SHOWNOTIFICATION = "shownotification";
public static final String CHANNEL_PLAYNOTIFICATION = "playnotification";
public static final String CHANNEL_PROFILE = "profile";
public static final String CHANNEL_INPUT = "input";
public static final String CHANNEL_INPUTTEXT = "inputtext";
public static final String CHANNEL_INPUTACTION = "inputaction";
public static final String CHANNEL_SYSTEMCOMMAND = "systemcommand";
public static final String CHANNEL_ARTIST = "artist";
public static final String CHANNEL_TITLE = "title";
public static final String CHANNEL_ORIGINALTITLE = "originaltitle";
public static final String CHANNEL_SHOWTITLE = "showtitle";
public static final String CHANNEL_ALBUM = "album";
public static final String CHANNEL_MEDIATYPE = "mediatype";
public static final String CHANNEL_MEDIAID = "mediaid";
public static final String CHANNEL_MEDIAFILE = "mediafile";
public static final String CHANNEL_GENRELIST = "genreList";
public static final String CHANNEL_PVR_CHANNEL = "pvr-channel";
public static final String CHANNEL_THUMBNAIL = "thumbnail";
public static final String CHANNEL_FANART = "fanart";
public static final String CHANNEL_AUDIO_CODEC = "audio-codec";
public static final String CHANNEL_AUDIO_CHANNELS = "audio-channels";
public static final String CHANNEL_AUDIO_INDEX = "audio-index";
public static final String CHANNEL_AUDIO_LANGUAGE = "audio-language";
public static final String CHANNEL_AUDIO_NAME = "audio-name";
public static final String CHANNEL_VIDEO_CODEC = "video-codec";
public static final String CHANNEL_VIDEO_INDEX = "video-index";
public static final String CHANNEL_VIDEO_HEIGHT = "video-height";
public static final String CHANNEL_VIDEO_WIDTH = "video-width";
public static final String CHANNEL_SUBTITLE_ENABLED = "subtitle-enabled";
public static final String CHANNEL_SUBTITLE_INDEX = "subtitle-index";
public static final String CHANNEL_SUBTITLE_LANGUAGE = "subtitle-language";
public static final String CHANNEL_SUBTITLE_NAME = "subtitle-name";
public static final String CHANNEL_CURRENTTIME = "currenttime";
public static final String CHANNEL_CURRENTTIMEPERCENTAGE = "currenttimepercentage";
public static final String CHANNEL_DURATION = "duration";
public static final String CHANNEL_UNIQUEID_IMDB = "uniqueid-imdb";
public static final String CHANNEL_UNIQUEID_IMDBTVSHOW = "uniqueid-imdbtvshow";
public static final String CHANNEL_UNIQUEID_TMDB = "uniqueid-tmdb";
public static final String CHANNEL_UNIQUEID_TMDBTVSHOW = "uniqueid-tmdbtvshow";
public static final String CHANNEL_UNIQUEID_TMDBEPISODE = "uniqueid-tmdbepisode";
public static final String CHANNEL_UNIQUEID_DOUBAN = "uniqueid-douban";
public static final String CHANNEL_MPAA = "mpaa";
public static final String CHANNEL_RATING = "rating";
public static final String CHANNEL_USERRATING = "userrating";
public static final String CHANNEL_SEASON = "season";
public static final String CHANNEL_EPISODE = "episode";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION = "shownotification";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_TITLE = "title";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_DISPLAYTIME = "displayTime";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_ICON = "icon";
// Module Properties
public static final String PROPERTY_VERSION = "version";
// Used for Discovery service
public static final String MANUFACTURER = "XBMC Foundation";
public static final String UPNP_DEVICE_TYPE = "MediaRenderer";
public static final String PVR_TV = "tv";
public static final String PVR_RADIO = "radio";
}

View File

@@ -0,0 +1,36 @@
/**
* 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.kodi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of command options.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = { DynamicCommandDescriptionProvider.class, KodiDynamicCommandDescriptionProvider.class })
@NonNullByDefault
public class KodiDynamicCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider {
@Activate
public KodiDynamicCommandDescriptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -0,0 +1,36 @@
/**
* 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.kodi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, KodiDynamicStateDescriptionProvider.class })
@NonNullByDefault
public class KodiDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public KodiDynamicStateDescriptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.kodi.internal;
import java.util.EventListener;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.kodi.internal.model.KodiAudioStream;
import org.openhab.binding.kodi.internal.model.KodiSubtitle;
import org.openhab.binding.kodi.internal.model.KodiSystemProperties;
import org.openhab.binding.kodi.internal.protocol.KodiConnection;
import org.openhab.core.library.types.RawType;
/**
* Interface which has to be implemented by a class in order to get status
* updates from a {@link KodiConnection}
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Added channels for opening PVR TV or Radio streams
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public interface KodiEventListener extends EventListener {
public enum KodiState {
PLAY,
PAUSE,
END,
STOP,
REWIND,
FASTFORWARD
}
public enum KodiPlaylistState {
ADD,
ADDED,
INSERT,
REMOVE,
REMOVED,
CLEAR
}
void updateConnectionState(boolean connected);
void updateScreenSaverState(boolean screenSaveActive);
void updatePlaylistState(KodiPlaylistState playlistState);
void updateVolume(int volume);
void updatePlayerState(KodiState state);
void updateMuted(boolean muted);
void updateMediaID(int mediaid);
void updateUniqueIDDouban(String uniqueid);
void updateUniqueIDImdb(String uniqueid);
void updateUniqueIDTmdb(String uniqueid);
void updateUniqueIDImdbtvshow(String uniqueid);
void updateUniqueIDTmdbtvshow(String uniqueid);
void updateUniqueIDTmdbepisode(String uniqueid);
void updateTitle(String title);
void updateOriginalTitle(String originaltitle);
void updateShowTitle(String title);
void updateAlbum(String album);
void updateArtistList(List<String> artistList);
void updateMediaType(String mediaType);
void updateGenreList(List<String> genreList);
void updatePVRChannel(String channel);
void updateThumbnail(@Nullable RawType thumbnail);
void updateFanart(@Nullable RawType fanart);
void updateAudioStreamOptions(List<KodiAudioStream> audioStreamList);
void updateAudioCodec(String codec);
void updateAudioName(String name);
void updateAudioIndex(int index);
void updateAudioChannels(int channels);
void updateAudioLanguage(String language);
void updateVideoCodec(String codec);
void updateVideoIndex(int index);
void updateVideoWidth(int width);
void updateVideoHeight(int height);
void updateSubtitleOptions(List<KodiSubtitle> subtitleList);
void updateSubtitleEnabled(boolean enabled);
void updateSubtitleIndex(int index);
void updateSubtitleName(String name);
void updateSubtitleLanguage(String language);
void updateCurrentTime(long currentTime);
void updateCurrentTimePercentage(double currentTimePercentage);
void updateDuration(long duration);
void updateSystemProperties(@Nullable KodiSystemProperties systemProperties);
void updateEpisode(int episode);
void updateSeason(int season);
void updateMediaFile(String mediafile);
void updateRating(double rating);
void updateUserRating(double rating);
void updateMpaa(String mpaa);
void updateCurrentProfile(String profile);
}

View File

@@ -0,0 +1,144 @@
/**
* 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.kodi.internal;
import static org.openhab.binding.kodi.internal.KodiBindingConstants.*;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.kodi.internal.handler.KodiHandler;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KodiHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Improvements on channels for opening PVR TV or Radio streams
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.kodi")
public class KodiHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(KodiHandlerFactory.class);
private final AudioHTTPServer audioHTTPServer;
private final NetworkAddressService networkAddressService;
private final KodiDynamicCommandDescriptionProvider commandDescriptionProvider;
private final KodiDynamicStateDescriptionProvider stateDescriptionProvider;
private final WebSocketClient webSocketClient;
private final Map<String, @Nullable ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();
// url (scheme+server+port) to use for playing notification sounds
private @Nullable String callbackUrl;
@Activate
public KodiHandlerFactory(final @Reference AudioHTTPServer audioHTTPServer,
final @Reference NetworkAddressService networkAddressService,
final @Reference KodiDynamicCommandDescriptionProvider commandDescriptionProvider,
final @Reference KodiDynamicStateDescriptionProvider stateDescriptionProvider,
final @Reference WebSocketFactory webSocketFactory) {
this.audioHTTPServer = audioHTTPServer;
this.networkAddressService = networkAddressService;
this.commandDescriptionProvider = commandDescriptionProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
this.webSocketClient = webSocketFactory.getCommonWebSocketClient();
}
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
Dictionary<String, Object> properties = componentContext.getProperties();
callbackUrl = (String) properties.get("callbackUrl");
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_KODI)) {
String callbackUrl = createCallbackUrl();
KodiHandler handler = new KodiHandler(thing, commandDescriptionProvider, stateDescriptionProvider,
webSocketClient, callbackUrl);
// register the Kodi as an audio sink
KodiAudioSink audioSink = new KodiAudioSink(handler, audioHTTPServer, callbackUrl);
@SuppressWarnings("unchecked")
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
audioSinkRegistrations.put(thing.getUID().toString(), reg);
return handler;
}
return null;
}
private @Nullable String createCallbackUrl() {
if (callbackUrl != null) {
return callbackUrl;
} else {
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.warn("No network interface could be found.");
return null;
}
// we do not use SSL as it can cause certificate validation issues.
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (port == -1) {
logger.warn("Cannot find port of the http service.");
return null;
}
return "http://" + ipAddress + ":" + port;
}
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ServiceRegistration<AudioSink> reg = audioSinkRegistrations.get(thing.getUID().toString());
if (reg != null) {
reg.unregister();
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.kodi.internal;
import org.openhab.binding.kodi.internal.KodiEventListener.KodiState;
/**
* The {@link KodiPlayerState} is responsible for saving the state of a player.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPlayerState {
private int savedPlaylistID;
private int savedVolume;
private KodiState savedState;
public int getSavedPlaylistID() {
return savedPlaylistID;
}
public void setPlaylistID(int savedPlaylistID) {
this.savedPlaylistID = savedPlaylistID;
}
public int getSavedVolume() {
return savedVolume;
}
public void setSavedVolume(int savedVolume) {
this.savedVolume = savedVolume;
}
public KodiState getSavedState() {
return savedState;
}
public void setSavedState(KodiState savedState) {
this.savedState = savedState;
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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.kodi.internal.config;
/**
* Channel configuration from openHAB.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiChannelConfig {
private String group;
public String getGroup() {
return group;
}
public void setGroup(final String group) {
this.group = group;
}
}

View File

@@ -0,0 +1,94 @@
/**
* 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.kodi.internal.config;
/**
* Thing configuration from openHAB.
*
* @author Christoph Weitkamp - Initial contribution
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public class KodiConfig {
private String ipAddress;
private Integer port;
private Integer httpPort;
private String httpUser;
private String httpPassword;
private Integer refreshInterval;
private Integer notificationTimeout;
private Integer notificationVolume;
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 Integer getHttpPort() {
return httpPort;
}
public void setHttpPort(Integer httpPort) {
this.httpPort = httpPort;
}
public String getHttpUser() {
return httpUser;
}
public void setHttpUser(String httpUser) {
this.httpUser = httpUser;
}
public String getHttpPassword() {
return httpPassword;
}
public void setHttpPassword(String httpPassword) {
this.httpPassword = httpPassword;
}
public Integer getRefreshInterval() {
return refreshInterval;
}
public void setRefreshInterval(Integer refreshInterval) {
this.refreshInterval = refreshInterval;
}
public Integer getNotificationTimeout() {
return notificationTimeout;
}
public void setNotificationTimeout(Integer notificationTimeout) {
this.notificationTimeout = notificationTimeout;
}
public Integer getNotificationVolume() {
return notificationVolume;
}
public void setNotificationVolume(Integer notificationVolume) {
this.notificationVolume = notificationVolume;
}
}

View File

@@ -0,0 +1,111 @@
/**
* 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.kodi.internal.discovery;
import static org.openhab.binding.kodi.internal.KodiBindingConstants.*;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An UpnpDiscoveryParticipant which allows to discover Kodi AVRs.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Use "discovery.kodi:background=false" to disable discovery service
*/
@Component(immediate = true, configurationPid = "discovery.kodi")
@NonNullByDefault
public class KodiUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
private Logger logger = LoggerFactory.getLogger(KodiUpnpDiscoveryParticipant.class);
private boolean isAutoDiscoveryEnabled = true;
@Activate
protected void activate(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
@Modified
protected void modified(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
private void activateOrModifyService(ComponentContext componentContext) {
Dictionary<String, @Nullable Object> properties = componentContext.getProperties();
String autoDiscoveryPropertyValue = (String) properties.get("background");
if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isEmpty()) {
isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
}
}
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return SUPPORTED_THING_TYPES_UIDS;
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
if (isAutoDiscoveryEnabled) {
ThingUID thingUid = getThingUID(device);
if (thingUid != null) {
String friendlyName = device.getDetails().getFriendlyName();
String label = friendlyName == null || friendlyName.isEmpty() ? device.getDisplayString()
: friendlyName;
Map<String, Object> properties = new HashMap<>();
properties.put(HOST_PARAMETER, device.getIdentity().getDescriptorURL().getHost());
DiscoveryResult result = DiscoveryResultBuilder.create(thingUid).withLabel(label)
.withProperties(properties).withRepresentationProperty(HOST_PARAMETER).build();
return result;
}
}
return null;
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
String manufacturer = device.getDetails().getManufacturerDetails().getManufacturer();
if (containsIgnoreCase(manufacturer, MANUFACTURER)) {
logger.debug("Manufacturer matched: search: {}, device value: {}.", MANUFACTURER, manufacturer);
String type = device.getType().getType();
if (containsIgnoreCase(type, UPNP_DEVICE_TYPE)) {
logger.debug("Device type matched: search: {}, device value: {}.", UPNP_DEVICE_TYPE, type);
return new ThingUID(THING_TYPE_KODI, device.getIdentity().getUdn().getIdentifierString());
}
}
return null;
}
private boolean containsIgnoreCase(final @Nullable String str, final String searchStr) {
return str != null && str.toLowerCase().contains(searchStr.toLowerCase());
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi audio stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Audio.Stream)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiAudioStream {
private int bitrate;
private int channels;
private @NonNullByDefault({}) String codec;
private int index;
private @NonNullByDefault({}) String language;
private @NonNullByDefault({}) String name;
public int getBitrate() {
return bitrate;
}
public void setBitrate(int bitrate) {
this.bitrate = bitrate;
}
public int getChannels() {
return channels;
}
public void setChannels(int channels) {
this.channels = channels;
}
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.kodi.internal.model;
/**
* Class representing a Kodi base item
*
* @author Christoph Weitkamp - Initial contribution
*/
public abstract class KodiBaseItem {
/**
* The label of the item
*/
private String label;
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
}

View File

@@ -0,0 +1,93 @@
/**
* 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.kodi.internal.model;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi duration (https://kodi.wiki/view/JSON-RPC_API/v9#Global.Time)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiDuration {
/**
* The hours of the duration
*/
private long hours;
/**
* The minutes of the duration
*/
private long minutes;
/**
* The seconds of the duration
*/
private long seconds;
/**
* The milliseconds of the duration
*/
private long milliseconds;
public long getHours() {
return hours;
}
public void setHours(long hours) {
this.hours = hours;
}
public long getMinutes() {
return minutes;
}
public void setMinutes(long minutes) {
this.minutes = minutes;
}
public long getSeconds() {
return seconds;
}
public void setSeconds(long seconds) {
this.seconds = seconds;
}
public long getMilliseconds() {
return milliseconds;
}
public void setMilliseconds(long milliseconds) {
this.milliseconds = milliseconds;
}
/**
* Converts this KodiDuration to the total length in seconds.
*
* @return the total length of the duration in seconds
*/
public long toSeconds() {
return TimeUnit.MILLISECONDS.toSeconds(toMillis());
}
/**
* Converts this KodiDuration to the total length in milliseconds.
*
* @return the total length of the duration in milliseconds
*/
public long toMillis() {
return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds).plusMillis(milliseconds).toMillis();
}
}

View File

@@ -0,0 +1,161 @@
/**
* 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.kodi.internal.model;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Class representing a Kodi favorite.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiFavorite {
// handle titles which are wrapped e.g. [COLOR FFE95E01]Title[/COLOR]
private static final Pattern TITLE_PATTERN = Pattern.compile("(\\[COLOR\\s\\w{8}\\])|(\\[/COLOR\\])");
/**
* The title of the favorite
*/
private String title;
/**
* The type of the favorite
*/
private String favoriteType = "unknown";
/**
* The path of the favorite
*/
@Nullable
private String path;
/**
* The window of the favorite
*/
@Nullable
private String window;
/**
* The parameters of the favorites window
*/
@Nullable
private String windowParameter;
/**
* Constructs a favorite with the given title.
*
* @param title title of the favorite
*/
public KodiFavorite(final String title) {
this.title = title;
}
/**
* Returns the title of the favorite.
*
* @return the title of the favorite
*/
public String getTitle() {
return title;
}
/**
* Sets the title of the favorite.
*
* @param title title of the favorite
*/
public void setTitle(final String title) {
Matcher m = TITLE_PATTERN.matcher(title);
this.title = m.replaceAll("");
}
/**
* Returns the type of the favorite.
*
* @return the type of the favorite
*/
public String getFavoriteType() {
return favoriteType;
}
/**
* Sets the type of the favorite.
*
* @param favoriteType type of the favorite. Valid values are: "media", "window", "script" or "unknown"
*/
public void setFavoriteType(final String favoriteType) {
this.favoriteType = favoriteType;
}
/**
* Returns the path of the favorite.
*
* @return the path of the favorite
*/
@Nullable
public String getPath() {
return path;
}
/**
* Sets the path of the favorite.
*
* @param path path of the favorite
*/
public void setPath(final String path) {
this.path = path;
}
/**
* Returns the window of the favorite.
*
* @return the window of the favorite
*/
@Nullable
public String getWindow() {
return window;
}
/**
* Sets the window of the favorite.
*
* @param window the window of the favorite
*/
public void setWindow(final String window) {
this.window = window;
}
/**
* Returns the parameters of the favorites window.
*
* @return the parameters of the favorites window
*/
@Nullable
public String getWindowParameter() {
return windowParameter;
}
/**
* Sets the parameters of the favorites window.
*
* @param windowParameter the parameters of the favorites window
*/
public void setWindowParameter(final String windowParameter) {
this.windowParameter = windowParameter;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.kodi.internal.model;
/**
* Class representing a Kodi PVR channel
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPVRChannel extends KodiBaseItem {
/**
* The PVR channel id
*/
private int channelId;
/**
* The PVR channel group id
*/
private int channelGroupId;
public int getId() {
return channelId;
}
public void setId(int channelId) {
this.channelId = channelId;
}
public int getChannelGroupId() {
return channelGroupId;
}
public void setChannelGroupId(int channelGroupId) {
this.channelGroupId = channelGroupId;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.kodi.internal.model;
/**
* Class representing a Kodi PVR channel group
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPVRChannelGroup extends KodiBaseItem {
/**
* The PVR channel group id
*/
private int channelGroupId;
/**
* The PVR channel type
*/
private String channelType;
public int getId() {
return channelGroupId;
}
public void setId(final int channelGroupId) {
this.channelGroupId = channelGroupId;
}
public String getChannelType() {
return channelType;
}
public void setChannelType(final String channelType) {
this.channelType = channelType;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi profile (see https://kodi.wiki/view/JSON-RPC_API/v8#Profiles.GetProfiles)
*
* @author Jan Hendriks - Initial contribution
*/
@NonNullByDefault
public class KodiProfile extends KodiBaseItem {
private int lockmode;
private String thumbnail = "";
public int getLockmode() {
return lockmode;
}
public void setLockmode(int lockmode) {
this.lockmode = lockmode;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
}

View File

@@ -0,0 +1,51 @@
/**
* 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi subtitle stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Subtitle)
*
* @author Meng Yiqi - Initial contribution
*/
@NonNullByDefault
public class KodiSubtitle {
private int index;
private String language = "";
private String name = "";
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,70 @@
/**
* 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* Class representing Kodi system properties (https://kodi.wiki/view/JSON-RPC_API/v9#System.Property.Value)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiSystemProperties {
@SerializedName("canhibernate")
private boolean canHibernate;
@SerializedName("canreboot")
private boolean canReboot;
@SerializedName("cansuspend")
private boolean canSuspend;
@SerializedName("canshutdown")
private boolean canShutdown;
public boolean canHibernate() {
return canHibernate;
}
public void setCanHibernate(boolean canHibernate) {
this.canHibernate = canHibernate;
}
public boolean canReboot() {
return canReboot;
}
public void setCanReboot(boolean canReboot) {
this.canReboot = canReboot;
}
public boolean canSuspend() {
return canSuspend;
}
public void setCansuspend(boolean canSuspend) {
this.canSuspend = canSuspend;
}
public boolean canShutdown() {
return canShutdown;
}
public void setCanShutdown(boolean canShutdown) {
this.canShutdown = canShutdown;
}
public boolean canQuit() {
return !canHibernate && !canReboot && !canShutdown && !canSuspend;
}
}

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.kodi.internal.model;
/**
* Class representing a Kodi UniqueID
*
* @author Meng Yiqi - Initial contribution
*/
public class KodiUniqueID {
private String imdb;
private String tmdb;
private String imdbtvshow;
private String tmdbtvshow;
private String tmdbepisode;
private String douban;
public String getImdb() {
return imdb;
}
public void setImdb(String imdb) {
this.imdb = imdb;
}
public String getTmdb() {
return tmdb;
}
public void setTmdb(String tmdb) {
this.tmdb = tmdb;
}
public String getImdbtvshow() {
return imdbtvshow;
}
public void setImdbtvshow(String imdbtvshow) {
this.imdbtvshow = imdbtvshow;
}
public String getTmdbtvshow() {
return tmdbtvshow;
}
public void setTmdbtvshow(String tmdbtvshow) {
this.tmdbtvshow = tmdbtvshow;
}
public String getTmdbepisode() {
return tmdbepisode;
}
public void setTmdbepisode(String tmdbepisode) {
this.tmdbtvshow = tmdbepisode;
}
public String getDouban() {
return douban;
}
public void setDouban(String douban) {
this.douban = douban;
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi video stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Video.Stream)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiVideoStream {
private @NonNullByDefault({}) String codec;
private int height;
private int index;
private @NonNullByDefault({}) String language;
private @NonNullByDefault({}) String name;
private int width;
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
}

View File

@@ -0,0 +1,227 @@
/**
* 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.kodi.internal.protocol;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* KodiClientSocket implements the low level communication to Kodi through
* websocket. Usually this communication is done through port 9090
*
* @author Paul Frank - Initial contribution
*/
public class KodiClientSocket {
private final Logger logger = LoggerFactory.getLogger(KodiClientSocket.class);
private final ScheduledExecutorService scheduler;
private static final int REQUEST_TIMEOUT_MS = 60000;
private CountDownLatch commandLatch = null;
private JsonObject commandResponse = null;
private int nextMessageId = 1;
private boolean connected = false;
private final JsonParser parser = new JsonParser();
private final Gson mapper = new Gson();
private final URI uri;
private final WebSocketClient client;
private Session session;
private Future<?> sessionFuture;
private final KodiClientSocketEventListener eventHandler;
public KodiClientSocket(KodiClientSocketEventListener eventHandler, URI uri, ScheduledExecutorService scheduler,
WebSocketClient webSocketClient) {
this.eventHandler = eventHandler;
this.uri = uri;
this.scheduler = scheduler;
this.client = webSocketClient;
}
/**
* Attempts to create a connection to the Kodi host and begin listening for updates over the async http web socket
*
* @throws IOException
*/
public synchronized void open() throws IOException {
if (isConnected()) {
logger.warn("open: connection is already open");
}
KodiWebSocketListener socket = new KodiWebSocketListener();
ClientUpgradeRequest request = new ClientUpgradeRequest();
sessionFuture = client.connect(socket, uri, request);
}
/***
* Close this connection to the Kodi instance
*/
public void close() {
// if there is an old web socket then clean up and destroy
if (session != null) {
session.close();
session = null;
}
if (sessionFuture != null && !sessionFuture.isDone()) {
sessionFuture.cancel(true);
}
}
public boolean isConnected() {
if (session == null || !session.isOpen()) {
return false;
}
return connected;
}
@WebSocket
public class KodiWebSocketListener {
@OnWebSocketConnect
public void onConnect(Session wssession) {
logger.trace("Connected to server");
session = wssession;
connected = true;
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.onConnectionOpened();
} catch (Exception e) {
logger.debug("Error handling onConnectionOpened(): {}", e.getMessage(), e);
}
});
}
}
@OnWebSocketMessage
public void onMessage(String message) {
logger.trace("Message received from server: {}", message);
final JsonObject json = parser.parse(message).getAsJsonObject();
if (json.has("id")) {
int messageId = json.get("id").getAsInt();
if (messageId == nextMessageId - 1) {
commandResponse = json;
commandLatch.countDown();
}
} else {
logger.trace("Event received from server: {}", json);
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.handleEvent(json);
} catch (Exception e) {
logger.debug("Error handling event {} player state change message: {}", json,
e.getMessage(), e);
}
});
}
}
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
logger.trace("Closing a WebSocket due to {}", reason);
session = null;
connected = false;
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.onConnectionClosed();
} catch (Exception e) {
logger.debug("Error handling onConnectionClosed(): {}", e.getMessage(), e);
}
});
}
}
@OnWebSocketError
public void onError(Throwable error) {
logger.trace("Error occured: {}", error.getMessage());
onClose(0, error.getMessage());
}
}
private void sendMessage(String str) throws IOException {
if (isConnected()) {
logger.trace("send message: {}", str);
session.getRemote().sendString(str);
} else {
throw new IOException("Socket not initialized");
}
}
public JsonElement callMethod(String methodName) {
return callMethod(methodName, null);
}
public synchronized JsonElement callMethod(String methodName, JsonObject params) {
try {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("jsonrpc", "2.0");
payloadObject.addProperty("id", nextMessageId);
payloadObject.addProperty("method", methodName);
if (params != null) {
payloadObject.add("params", params);
}
String message = mapper.toJson(payloadObject);
commandLatch = new CountDownLatch(1);
commandResponse = null;
nextMessageId++;
sendMessage(message);
if (commandLatch.await(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
logger.debug("callMethod returns: {}", commandResponse);
if (commandResponse.has("result")) {
return commandResponse.get("result");
} else {
JsonElement error = commandResponse.get("error");
logger.debug("Error received from server: {}", error);
return null;
}
} else {
logger.debug("Timeout during callMethod({}, {})", methodName, params);
return null;
}
} catch (IOException | InterruptedException e) {
logger.debug("Error during callMethod({}, {}): {}", methodName, params, e.getMessage(), e);
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.kodi.internal.protocol;
import com.google.gson.JsonObject;
/**
* This interface has to be implemented for classes which need to be able to receive events from KodiClientSocket
*
* @author Paul Frank - Initial contribution
*/
public interface KodiClientSocketEventListener {
void handleEvent(JsonObject json);
void onConnectionClosed();
void onConnectionOpened();
}

View File

@@ -0,0 +1,307 @@
/**
* 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.kodi.internal.utils;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a simple file based cache implementation.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class ByteArrayFileCache {
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
private static final String MD5_ALGORITHM = "MD5";
static final String CACHE_FOLDER_NAME = "cache";
private static final char EXTENSION_SEPARATOR = '.';
private static final char UNIX_SEPARATOR = '/';
private static final char WINDOWS_SEPARATOR = '\\';
private final File cacheFolder;
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
private int expiry = 0;
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
/**
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
* <code>$userdata/cache/$servicePID</code>.
*
* @param servicePID PID of the service
*/
public ByteArrayFileCache(String servicePID) {
// TODO track and limit folder size
// TODO support user specific folder
cacheFolder = new File(new File(ConfigConstants.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
if (!cacheFolder.exists()) {
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
cacheFolder.mkdirs();
}
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
}
/**
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
* <code>$userdata/cache/$servicePID/</code>.
*
* @param servicePID PID of the service
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
* disables this functionality.
*/
public ByteArrayFileCache(String servicePID, int expiry) {
this(servicePID);
if (expiry < 0) {
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
}
this.expiry = expiry;
}
/**
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
* new content.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
*/
public void put(String key, byte[] content) {
writeFile(getUniqueFile(key), content);
}
/**
* Adds a file to the cache.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
*/
public void putIfAbsent(String key, byte[] content) {
File fileInCache = getUniqueFile(key);
if (fileInCache.exists()) {
logger.debug("File '{}' present in cache", fileInCache.getName());
// update time of last use
fileInCache.setLastModified(System.currentTimeMillis());
} else {
writeFile(fileInCache, content);
}
}
/**
* Adds a file to the cache and returns the content of the file.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
* @return the content of the file associated with the given key
*/
public byte[] putIfAbsentAndGet(String key, byte[] content) {
putIfAbsent(key, content);
return content;
}
/**
* Writes the given content to the given {@link File}.
*
* @param fileInCache the {@link File}
* @param content the content to be written
*/
private void writeFile(File fileInCache, byte[] content) {
logger.debug("Caching file '{}'", fileInCache.getName());
try {
Files.write(fileInCache.toPath(), content);
} catch (IOException e) {
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
}
}
/**
* Checks if the key is present in the cache.
*
* @param key the key whose presence in the cache is to be tested
* @return true if the cache contains a file for the specified key
*/
public boolean containsKey(String key) {
return getUniqueFile(key).exists();
}
/**
* Removes the file associated with the given key from the cache.
*
* @param key the key whose associated file is to be removed
*/
public void remove(String key) {
deleteFile(getUniqueFile(key));
}
/**
* Deletes the given {@link File}.
*
* @param fileInCache the {@link File}
*/
private void deleteFile(File fileInCache) {
if (fileInCache.exists()) {
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
fileInCache.delete();
} else {
logger.debug("File '{}' not found in cache", fileInCache.getName());
}
}
/**
* Removes all files from the cache.
*/
public void clear() {
File[] filesInCache = cacheFolder.listFiles();
if (filesInCache != null && filesInCache.length > 0) {
logger.debug("Deleting all files from cache");
Arrays.stream(filesInCache).forEach(File::delete);
}
}
/**
* Removes expired files from the cache.
*/
public void clearExpired() {
// exit if expiry is set to 0 (disabled)
if (expiry <= 0) {
return;
}
File[] filesInCache = cacheFolder.listFiles();
if (filesInCache != null && filesInCache.length > 0) {
logger.debug("Deleting expired files from cache");
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
}
}
/**
* Checks if the given {@link File} is expired.
*
* @param fileInCache the {@link File}
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
*/
private boolean isExpired(File fileInCache) {
// exit if expiry is set to 0 (disabled)
if (expiry <= 0) {
return false;
}
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
}
/**
* Returns the content of the file associated with the given key, if it is present.
*
* @param key the key whose associated file is to be returned
* @return the content of the file associated with the given key
*/
public byte[] get(String key) {
return readFile(getUniqueFile(key));
}
/**
* Reads the content from the given {@link File}, if it is present.
*
* @param fileInCache the {@link File}
* @return the content of the file
*/
private byte[] readFile(File fileInCache) {
if (fileInCache.exists()) {
logger.debug("Reading file '{}' from cache", fileInCache.getName());
// update time of last use
fileInCache.setLastModified(System.currentTimeMillis());
try {
return Files.readAllBytes(fileInCache.toPath());
} catch (IOException e) {
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
}
} else {
logger.debug("File '{}' not found in cache", fileInCache.getName());
}
return new byte[0];
}
/**
* Creates a unique {@link File} from the key with which the file is to be associated.
*
* @param key the key with which the file is to be associated
* @return unique file for the file associated with the given key
*/
File getUniqueFile(String key) {
String uniqueFileName = getUniqueFileName(key);
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
return FILES_IN_CACHE.get(uniqueFileName);
} else {
String fileExtension = getFileExtension(key);
File fileInCache = new File(cacheFolder,
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
return fileInCache;
}
}
/**
* Gets the extension of a file name.
*
* @param fileName the file name to retrieve the extension of
* @return the extension of the file or null if none exists
*/
@Nullable
String getFileExtension(String fileName) {
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1).replaceFirst("\\?.*$", "");
}
/**
* Creates a unique file name from the key with which the file is to be associated.
*
* @param key the key with which the file is to be associated
* @return unique file name for the file associated with the given key
*/
String getUniqueFileName(String key) {
try {
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
byte[] bytesOfKey = key.getBytes(StandardCharsets.UTF_8);
byte[] md5Hash = md.digest(bytesOfKey);
BigInteger bigInt = new BigInteger(1, md5Hash);
String fileNameHash = bigInt.toString(16);
// We need to zero pad it if you actually want the full 32 chars
while (fileNameHash.length() < 32) {
fileNameHash = "0" + fileNameHash;
}
return fileNameHash;
} catch (NoSuchAlgorithmException ex) {
// should not happen
logger.error("Could not create MD5 hash for key '{}'", key, ex);
return key;
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="kodi" 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>Kodi Binding</name>
<description>This is the binding for Kodi.</description>
<author>Paul Frank</author>
<config-description>
<parameter name="callbackUrl" type="text">
<label>Callback URL</label>
<description>url to use for playing notification sounds, e.g. http://192.168.0.2:8080</description>
<required>false</required>
</parameter>
</config-description>
</binding:binding>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:kodi:kodi">
<parameter-group name="network">
<label>Network</label>
<description>Network settings.</description>
</parameter-group>
<parameter-group name="connection">
<label>Connection</label>
<description>Connection settings.</description>
</parameter-group>
<parameter-group name="notification">
<label>Notification</label>
<description>Notification settings.</description>
</parameter-group>
<parameter name="ipAddress" type="text" required="true" groupName="network">
<label>Network Address</label>
<description>The IP or host name of the Kodi</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" required="true" min="1" max="65535" groupName="network">
<label>Web Socket Service Port</label>
<description>Port for the web socket service</description>
<default>9090</default>
</parameter>
<parameter name="httpPort" type="integer" required="true" min="1" max="65535" groupName="network">
<label>HTTP Service Port</label>
<description>Port for the HTTP service.</description>
<default>8080</default>
<advanced>true</advanced>
</parameter>
<parameter name="httpUser" type="text" required="false" groupName="network">
<label>Username</label>
<description>User name to access HTTP service.</description>
<advanced>true</advanced>
</parameter>
<parameter name="httpPassword" type="text" required="false" groupName="network">
<context>password</context>
<label>Password</label>
<description>Password to access the HTTP service.</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" required="false" min="1" max="60" unit="s"
groupName="connection">
<label>Refresh Interval</label>
<description>The refresh interval to poll Kodi API (in s).</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="notificationTimeout" type="integer" unit="s" required="true" groupName="notification">
<label>Notification Timeout</label>
<description>Maximum amount of time for which the notification will be played (in s).</description>
<default>20</default>
</parameter>
<parameter name="notificationVolume" type="integer" min="0" max="100" step="1" unit="%"
groupName="notification">
<label>Notification Sound Volume</label>
<description>Specifies the volume applied to a notification sound (in %).</description>
</parameter>
</config-description>
<config-description uri="channel-type:kodi:pvr-channel">
<parameter name="group" type="text" required="true">
<label>PVR Channel Group</label>
<description>The PVR channel group name used to identify the channel id.</description>
<default>All channels</default>
</parameter>
</config-description>
<config-description uri="channel-type:kodi:shownotification">
<parameter name="title" type="text" required="false">
<label>Notification Title</label>
<description>Title to show for the notification.</description>
<default>openHAB</default>
</parameter>
<parameter name="displayTime" type="integer" required="false" min="100" unit="ms">
<label>Display Time</label>
<description>Time the notification is displayed.</description>
<default>5000</default>
</parameter>
<parameter name="icon" type="text" required="false">
<label>Icon</label>
<description>Icon name to show with the notification.</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,114 @@
# binding
binding.kodi.name = Kodi Binding
binding.kodi.description = Dieses Binding integriert das Kodi Media Center. Durch dieses kann das Media Center gesteuert werden.
# binding config
binding.config.kodi.callbackUrl.label = Callback URL
binding.config.kodi.callbackUrl.description = URL zum Abspielen von Benachrichtigungen (z.B. http://192.168.0.2:8080).
# thing types
thing-type.kodi.kodi.label = Kodi Media Center
thing-type.kodi.kodi.description = Kodi Media Center
# thing types config
thing-type.config.kodi.kodi.group.network.label = Netzwerk
thing-type.config.kodi.kodi.group.network.description = Einstellungen für das Netzwerk.
thing-type.config.kodi.kodi.group.connection.label = Verbindung
thing-type.config.kodi.kodi.group.connection.description = Einstellungen für die Verbindung.
thing-type.config.kodi.kodi.group.notification.label = Benachrichtigungen
thing-type.config.kodi.kodi.group.notification.description = Einstellungen für Benachrichtigungen.
thing-type.config.kodi.kodi.ipAddress.label = IP-Adresse
thing-type.config.kodi.kodi.ipAddress.description = Lokale IP-Adresse oder Hostname des Kodi Media Centers.
thing-type.config.kodi.kodi.port.label = Port (Webservice)
thing-type.config.kodi.kodi.port.description = Port des Kodi Media Centers Webservices.
thing-type.config.kodi.kodi.httpPort.label = Port (Weboberfläche)
thing-type.config.kodi.kodi.httpPort.description = Port der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.httpUser.label = Benutzer
thing-type.config.kodi.kodi.httpUser.description = Benutzer zur Authentifizierung an der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.httpPassword.label = Passwort
thing-type.config.kodi.kodi.httpPassword.description = Passwort zur Authentifizierung an der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.refreshInterval.label = Abfrageintervall
thing-type.config.kodi.kodi.refreshInterval.description = Intervall zur Abfrage des Kodi Media Centers (in s).
thing-type.config.kodi.kodi.notificationTimeout.label = Timeout für Benachrichtigungen
thing-type.config.kodi.kodi.notificationTimeout.description = Timeout für Benachrichtigungen (in s).
thing-type.config.kodi.kodi.notificationVolume.label = Lautstärke von Benachrichtigungen
thing-type.config.kodi.kodi.notificationVolume.description = Lautstärke von Benachrichtigungen (in %).
# channel types
channel-type.kodi.stop.label = Stop
channel-type.kodi.stop.description = Ermöglicht das Stoppen der Wiedergabe.
channel-type.kodi.playuri.label = URI abspielen
channel-type.kodi.playuri.description = Ermöglicht das Abspielen einer URI.
channel-type.kodi.playfavorite.label = Favorit abspielen oder öffnen
channel-type.kodi.playfavorite.description = Ermöglicht das Abspielen oder Öffnen eines Favoriten.
channel-type.kodi.pvr-open-tv.label = PVR TV Kanal
channel-type.kodi.pvr-open-tv.description = Ermöglicht das Abspielen eines PVR TV Kanals.
channel-type.kodi.pvr-open-radio.label = PVR Radio Kanal
channel-type.kodi.pvr-open-radio.description = Ermöglicht das Abspielen eines PVR Radio Kanals.
channel-type.kodi.pvr-channel.label = PVR Kanal
channel-type.kodi.pvr-channel.description = Zeigt den Titel des aktuellen PVR Kanals an.
channel-type.kodi.shownotification.label = Nachricht anzeigen
channel-type.kodi.shownotification.description = Ermöglicht das Anzeigen einer Nachricht auf dem Kodi Media Center.
channel-type.kodi.input.label = Tastendruck
channel-type.kodi.input.description = Ermöglicht das Senden eines Eingabebefehls zur Steuerung der UI an das Kodi Media Center.
channel-type.kodi.input.state.option.Back = Zurück
channel-type.kodi.input.state.option.ContextMenu = Kontextmenü
channel-type.kodi.input.state.option.Down = Runter
channel-type.kodi.input.state.option.Home = Home
channel-type.kodi.input.state.option.Info = Info Dialog
channel-type.kodi.input.state.option.Left = Links
channel-type.kodi.input.state.option.Right = Rechts
channel-type.kodi.input.state.option.Select = Auswählen
channel-type.kodi.input.state.option.ShowCodec = Codec Info
channel-type.kodi.input.state.option.ShowOSD = Bildschirmanzeige
channel-type.kodi.input.state.option.ShowPlayerProcessInfo = Player Info
channel-type.kodi.input.state.option.Up = Hoch
channel-type.kodi.inputtext.label = Texteingabe
channel-type.kodi.inputtext.description = Ermöglicht das Senden eines Eingabetextes (Unicode) an das Kodi Media Center.
channel-type.kodi.inputaction.label = Aktion
channel-type.kodi.inputaction.description = Ermöglicht das Senden einer vordefinierten Aktion an das Kodi Media Center.
channel-type.kodi.systemcommand.label = Systembefehl
channel-type.kodi.systemcommand.description = Ermöglicht das Senden eines Systembefehls um das Kodi Media Center neu zu starten, in den Ruhezustand oder Stromsparmodus zu versetzen oder herunterzufahren.
channel-type.kodi.systemcommand.command.option.Shutdown = Herunterfahren
channel-type.kodi.systemcommand.command.option.Suspend = Bereitschaft
channel-type.kodi.systemcommand.command.option.Hibernate = Ruhezustand
channel-type.kodi.systemcommand.command.option.Reboot = Neustart
channel-type.kodi.systemcommand.command.option.Quit = Verlassen
channel-type.kodi.showtitle.label = Show Titel
channel-type.kodi.showtitle.description = Zeigt den Titel der aktuellen Show an.
channel-type.kodi.album.label = Album
channel-type.kodi.album.description = Zeigt das Album des aktuellen Stücks an.
channel-type.kodi.mediatype.label = Medientyp
channel-type.kodi.mediatype.description = Zeigt den Medientyp des aktuellen Stücks oder Films (z. B. movie, song) an.
channel-type.kodi.genreList.label = Genre Liste
channel-type.kodi.genreList.description = Zeigt eine durch Komma getrennte Liste der Genres des aktuellen Stücks oder Films an.
channel-type.kodi.thumbnail.label = Thumbnail
channel-type.kodi.thumbnail.description = Zeigt das Thumbnail des aktuellen Stücks oder Films an.
channel-type.kodi.fanart.label = Fan Art
channel-type.kodi.fanart.description = Zeigt das Fan Art des aktuellen Stücks oder Films an.
channel-type.kodi.playnotification.label = Benachrichtigung abspielen
channel-type.kodi.playnotification.description = Ermöglicht das Abspielen einer Benachrichtigung.
channel-type.kodi.codec.label = Codec
channel-type.kodi.codec.description = Zeigt den Codec des aktuellen Stücks oder Films an.
thing-type.kodi.kodi.channel.audio-codec.label = Audio-Codec
thing-type.kodi.kodi.channel.audio-codec.description = Zeigt den Audio-Codec des aktuellen Stücks oder Films an.
thing-type.kodi.kodi.channel.video-codec.label = Video-Codec
thing-type.kodi.kodi.channel.video-codec.description = Zeigt den Video-Codec des aktuellen Stücks oder Films an.
channel-type.kodi.currenttime.label = Laufzeit
channel-type.kodi.currenttime.description = Zeigt die Laufzeit des aktuellen Stücks oder Films an.
channel-type.kodi.currenttimepercentage.label = Laufzeit in Prozent
channel-type.kodi.currenttimepercentage.description = Zeigt die Laufzeit des aktuellen Stücks oder Films an.
channel-type.kodi.duration.label = Dauer
channel-type.kodi.duration.description = Zeigt die Dauer des aktuellen Stücks oder Films an.
# channel types config
channel-type.config.kodi.pvr-channel.group.label = PVR Kanal Gruppe
channel-type.config.kodi.pvr-channel.group.description = Gruppe der verwendbaren PVR Kanäle.
channel-type.config.kodi.shownotification.title.label = Titel
channel-type.config.kodi.shownotification.title.description = Titel der Nachricht.
channel-type.config.kodi.shownotification.displayTime.label = Anzeigezeit
channel-type.config.kodi.shownotification.displayTime.description = Anzeigezeit der Nachricht.
channel-type.config.kodi.shownotification.icon.label = Icon
channel-type.config.kodi.shownotification.icon.description = Icon der Nachricht.

View File

@@ -0,0 +1,557 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="kodi"
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">
<!-- Kodi Thing Type -->
<thing-type id="kodi" extensible="shownotification">
<label>Kodi Mediacenter</label>
<description>Kodi Mediacenter Binding</description>
<channels>
<channel id="volume" typeId="system.volume"/>
<channel id="mute" typeId="system.mute"/>
<channel id="control" typeId="system.media-control"/>
<channel id="stop" typeId="stop"/>
<channel id="playuri" typeId="playuri"/>
<channel id="pvr-open-tv" typeId="pvr-open-tv"/>
<channel id="pvr-open-radio" typeId="pvr-open-radio"/>
<channel id="pvr-channel" typeId="pvr-channel"/>
<channel id="shownotification" typeId="shownotification"/>
<channel id="input" typeId="input"/>
<channel id="inputtext" typeId="inputtext"/>
<channel id="inputaction" typeId="inputaction"/>
<channel id="systemcommand" typeId="systemcommand"/>
<channel id="title" typeId="system.media-title"/>
<channel id="originaltitle" typeId="system.media-title"/>
<channel id="showtitle" typeId="showtitle"/>
<channel id="album" typeId="album"/>
<channel id="artist" typeId="system.media-artist"/>
<channel id="mediatype" typeId="mediatype"/>
<channel id="genreList" typeId="genreList"/>
<channel id="thumbnail" typeId="thumbnail"/>
<channel id="fanart" typeId="fanart"/>
<channel id="playfavorite" typeId="playfavorite"/>
<channel id="playnotification" typeId="playnotification"/>
<channel id="profile" typeId="profile"/>
<channel id="audio-name" typeId="name">
<label>Audio Name</label>
</channel>
<channel id="audio-channels" typeId="channels">
<label>Audio Channels</label>
</channel>
<channel id="audio-index" typeId="index">
<label>Audio Index</label>
</channel>
<channel id="audio-language" typeId="language">
<label>Audio Language</label>
</channel>
<channel id="audio-codec" typeId="codec">
<label>Audio Codec</label>
<description>Audio codec of currently playing media</description>
</channel>
<channel id="video-width" typeId="width"/>
<channel id="video-height" typeId="height"/>
<channel id="video-index" typeId="index">
<label>Video Index</label>
</channel>
<channel id="video-codec" typeId="codec">
<label>Video Codec</label>
<description>Video codec of currently playing media</description>
</channel>
<channel id="subtitle-enabled" typeId="enabled">
<label>Subtitle Enabled</label>
</channel>
<channel id="subtitle-name" typeId="name">
<label>Subtitle Name</label>
</channel>
<channel id="subtitle-index" typeId="index">
<label>Subtitle Index</label>
</channel>
<channel id="subtitle-language" typeId="language">
<label>Subtitle Language</label>
</channel>
<channel id="currenttime" typeId="currenttime"/>
<channel id="currenttimepercentage" typeId="currenttimepercentage"/>
<channel id="duration" typeId="duration"/>
<channel id="mediafile" typeId="file"/>
<channel id="mediaid" typeId="id">
<label>Kodi Media ID</label>
<description>media_id in kodi database</description>
</channel>
<channel id="uniqueid-douban" typeId="uniqueid">
<label>Douban ID</label>
<description>example usage - http://www.douban.com/subject/3036644/</description>
</channel>
<channel id="uniqueid-imdb" typeId="uniqueid">
<label>IMDB ID</label>
<description>example usage - http://www.imdb.com/title/tt7207268/</description>
</channel>
<channel id="uniqueid-imdbtvshow" typeId="uniqueid">
<label>IMDB TVSHOW ID</label>
<description>example usage - http://www.imdb.com/title/tt0426769/</description>
</channel>
<channel id="uniqueid-tmdb" typeId="uniqueid">
<label>TMDB ID</label>
<description>example usage - http://www.themoviedb.org/movie/123456789</description>
</channel>
<channel id="uniqueid-tmdbepisode" typeId="uniqueid">
<label>TMDB EPISODE ID</label>
<description>example usage - http://www.themoviedb.org/tv/12225/season/5/episode/15/</description>
</channel>
<channel id="uniqueid-tmdbtvshow" typeId="uniqueid">
<label>TMDB TVSHOW ID</label>
<description>example usage - http://www.themoviedb.org/tv/12225/</description>
</channel>
<channel id="mpaa" typeId="mpaa"/>
<channel id="rating" typeId="rating"/>
<channel id="userrating" typeId="rating">
<label>User Rating</label>
</channel>
</channels>
<properties>
<property name="version">unknown</property>
</properties>
<representation-property>ipAddress</representation-property>
<config-description-ref uri="thing-type:kodi:kodi"/>
</thing-type>
<!-- Kodi Commands -->
<channel-type id="stop">
<item-type>Switch</item-type>
<label>Stop</label>
<description>Stops the player. ON if the player is stopped.</description>
</channel-type>
<channel-type id="playuri" advanced="true">
<item-type>String</item-type>
<label>Play URI</label>
<description>Play the given URI</description>
</channel-type>
<channel-type id="playfavorite">
<item-type>String</item-type>
<label>Play or Open a Favorite</label>
<description>Play or open the given favorite by sending a command with the favorite's title</description>
</channel-type>
<channel-type id="pvr-open-tv" advanced="true">
<item-type>String</item-type>
<label>Play PVR TV</label>
<description>Play the given PVR TV channel by sending a command with the channel's name</description>
<state pattern="%s"/>
<config-description-ref uri="channel-type:kodi:pvr-channel"/>
</channel-type>
<channel-type id="pvr-open-radio" advanced="true">
<item-type>String</item-type>
<label>Play PVR Radio</label>
<description>Play the given PVR Radio channel by sending a command with the channel's name</description>
<state pattern="%s"/>
<config-description-ref uri="channel-type:kodi:pvr-channel"/>
</channel-type>
<channel-type id="shownotification" advanced="true">
<item-type>String</item-type>
<label>Show Notification</label>
<description>Shows a notification on the UI</description>
<config-description-ref uri="channel-type:kodi:shownotification"/>
</channel-type>
<channel-type id="playnotification" advanced="true">
<item-type>String</item-type>
<label>Play Notification</label>
<description>Plays a notification sound by a given URI</description>
</channel-type>
<channel-type id="input" advanced="true">
<item-type>String</item-type>
<label>Send a Key</label>
<description>Sends a key stroke to Kodi to navigate in the UI</description>
<state>
<options>
<option value="Back">Back</option>
<option value="ContextMenu">Show context Menu</option>
<option value="Down">Down</option>
<option value="Home">Home</option>
<option value="Info">Show information dialog</option>
<option value="Left">Left</option>
<option value="Right">Right</option>
<option value="Select">Select</option>
<option value="ShowCodec">Show codec information</option>
<option value="ShowOSD">Show on-screen display</option>
<option value="ShowPlayerProcessInfo">Show player process info</option>
<option value="Up">Up</option>
</options>
</state>
</channel-type>
<channel-type id="inputtext" advanced="true">
<item-type>String</item-type>
<label>Sends Text as Input</label>
<description>Sends a generic input (unicode) text to Kodi</description>
</channel-type>
<channel-type id="inputaction" advanced="true">
<item-type>String</item-type>
<label>Execute an Action</label>
<description>Sends a predefined action to Kodi to control the UI and/or perform other tasks</description>
<state>
<options>
<option value="left">left</option>
<option value="right">right</option>
<option value="up">up</option>
<option value="down">down</option>
<option value="pageup">pageup</option>
<option value="pagedown">pagedown</option>
<option value="select">select</option>
<option value="highlight">highlight</option>
<option value="parentdir">parentdir</option>
<option value="parentfolder">parentfolder</option>
<option value="back">back</option>
<option value="menu">menu</option>
<option value="previousmenu">previousmenu</option>
<option value="info">info</option>
<option value="pause">pause</option>
<option value="stop">stop</option>
<option value="skipnext">skipnext</option>
<option value="skipprevious">skipprevious</option>
<option value="fullscreen">fullscreen</option>
<option value="aspectratio">aspectratio</option>
<option value="stepforward">stepforward</option>
<option value="stepback">stepback</option>
<option value="bigstepforward">bigstepforward</option>
<option value="bigstepback">bigstepback</option>
<option value="chapterorbigstepforward">chapterorbigstepforward</option>
<option value="chapterorbigstepback">chapterorbigstepback</option>
<option value="osd">osd</option>
<option value="showsubtitles">showsubtitles</option>
<option value="nextsubtitle">nextsubtitle</option>
<option value="cyclesubtitle">cyclesubtitle</option>
<option value="playerdebug">playerdebug</option>
<option value="codecinfo">codecinfo</option>
<option value="playerprocessinfo">playerprocessinfo</option>
<option value="nextpicture">nextpicture</option>
<option value="previouspicture">previouspicture</option>
<option value="zoomout">zoomout</option>
<option value="zoomin">zoomin</option>
<option value="playlist">playlist</option>
<option value="queue">queue</option>
<option value="zoomnormal">zoomnormal</option>
<option value="zoomlevel1">zoomlevel1</option>
<option value="zoomlevel2">zoomlevel2</option>
<option value="zoomlevel3">zoomlevel3</option>
<option value="zoomlevel4">zoomlevel4</option>
<option value="zoomlevel5">zoomlevel5</option>
<option value="zoomlevel6">zoomlevel6</option>
<option value="zoomlevel7">zoomlevel7</option>
<option value="zoomlevel8">zoomlevel8</option>
<option value="zoomlevel9">zoomlevel9</option>
<option value="nextcalibration">nextcalibration</option>
<option value="resetcalibration">resetcalibration</option>
<option value="analogmove">analogmove</option>
<option value="analogmovex">analogmovex</option>
<option value="analogmovey">analogmovey</option>
<option value="rotate">rotate</option>
<option value="rotateccw">rotateccw</option>
<option value="close">close</option>
<option value="subtitledelayminus">subtitledelayminus</option>
<option value="subtitledelay">subtitledelay</option>
<option value="subtitledelayplus">subtitledelayplus</option>
<option value="audiodelayminus">audiodelayminus</option>
<option value="audiodelay">audiodelay</option>
<option value="audiodelayplus">audiodelayplus</option>
<option value="subtitleshiftup">subtitleshiftup</option>
<option value="subtitleshiftdown">subtitleshiftdown</option>
<option value="subtitlealign">subtitlealign</option>
<option value="audionextlanguage">audionextlanguage</option>
<option value="verticalshiftup">verticalshiftup</option>
<option value="verticalshiftdown">verticalshiftdown</option>
<option value="nextresolution">nextresolution</option>
<option value="audiotoggledigital">audiotoggledigital</option>
<option value="number0">number0</option>
<option value="number1">number1</option>
<option value="number2">number2</option>
<option value="number3">number3</option>
<option value="number4">number4</option>
<option value="number5">number5</option>
<option value="number6">number6</option>
<option value="number7">number7</option>
<option value="number8">number8</option>
<option value="number9">number9</option>
<option value="smallstepback">smallstepback</option>
<option value="fastforward">fastforward</option>
<option value="rewind">rewind</option>
<option value="play">play</option>
<option value="playpause">playpause</option>
<option value="switchplayer">switchplayer</option>
<option value="delete">delete</option>
<option value="copy">copy</option>
<option value="move">move</option>
<option value="screenshot">screenshot</option>
<option value="rename">rename</option>
<option value="togglewatched">togglewatched</option>
<option value="scanitem">scanitem</option>
<option value="reloadkeymaps">reloadkeymaps</option>
<option value="volumeup">volumeup</option>
<option value="volumedown">volumedown</option>
<option value="mute">mute</option>
<option value="backspace">backspace</option>
<option value="scrollup">scrollup</option>
<option value="scrolldown">scrolldown</option>
<option value="analogfastforward">analogfastforward</option>
<option value="analogrewind">analogrewind</option>
<option value="moveitemup">moveitemup</option>
<option value="moveitemdown">moveitemdown</option>
<option value="contextmenu">contextmenu</option>
<option value="shift">shift</option>
<option value="symbols">symbols</option>
<option value="cursorleft">cursorleft</option>
<option value="cursorright">cursorright</option>
<option value="showtime">showtime</option>
<option value="analogseekforward">analogseekforward</option>
<option value="analogseekback">analogseekback</option>
<option value="showpreset">showpreset</option>
<option value="nextpreset">nextpreset</option>
<option value="previouspreset">previouspreset</option>
<option value="lockpreset">lockpreset</option>
<option value="randompreset">randompreset</option>
<option value="increasevisrating">increasevisrating</option>
<option value="decreasevisrating">decreasevisrating</option>
<option value="showvideomenu">showvideomenu</option>
<option value="enter">enter</option>
<option value="increaserating">increaserating</option>
<option value="decreaserating">decreaserating</option>
<option value="setrating">setrating</option>
<option value="togglefullscreen">togglefullscreen</option>
<option value="nextscene">nextscene</option>
<option value="previousscene">previousscene</option>
<option value="nextletter">nextletter</option>
<option value="prevletter">prevletter</option>
<option value="jumpsms2">jumpsms2</option>
<option value="jumpsms3">jumpsms3</option>
<option value="jumpsms4">jumpsms4</option>
<option value="jumpsms5">jumpsms5</option>
<option value="jumpsms6">jumpsms6</option>
<option value="jumpsms7">jumpsms7</option>
<option value="jumpsms8">jumpsms8</option>
<option value="jumpsms9">jumpsms9</option>
<option value="filter">filter</option>
<option value="filterclear">filterclear</option>
<option value="filtersms2">filtersms2</option>
<option value="filtersms3">filtersms3</option>
<option value="filtersms4">filtersms4</option>
<option value="filtersms5">filtersms5</option>
<option value="filtersms6">filtersms6</option>
<option value="filtersms7">filtersms7</option>
<option value="filtersms8">filtersms8</option>
<option value="filtersms9">filtersms9</option>
<option value="firstpage">firstpage</option>
<option value="lastpage">lastpage</option>
<option value="guiprofile">guiprofile</option>
<option value="red">red</option>
<option value="green">green</option>
<option value="yellow">yellow</option>
<option value="blue">blue</option>
<option value="increasepar">increasepar</option>
<option value="decreasepar">decreasepar</option>
<option value="volampup">volampup</option>
<option value="volampdown">volampdown</option>
<option value="volumeamplification">volumeamplification</option>
<option value="createbookmark">createbookmark</option>
<option value="createepisodebookmark">createepisodebookmark</option>
<option value="settingsreset">settingsreset</option>
<option value="settingslevelchange">settingslevelchange</option>
<option value="stereomode">stereomode</option>
<option value="nextstereomode">nextstereomode</option>
<option value="previousstereomode">previousstereomode</option>
<option value="togglestereomode">togglestereomode</option>
<option value="stereomodetomono">stereomodetomono</option>
<option value="channelup">channelup</option>
<option value="channeldown">channeldown</option>
<option value="previouschannelgroup">previouschannelgroup</option>
<option value="nextchannelgroup">nextchannelgroup</option>
<option value="playpvr">playpvr</option>
<option value="playpvrtv">playpvrtv</option>
<option value="playpvrradio">playpvrradio</option>
<option value="record">record</option>
<option value="togglecommskip">togglecommskip</option>
<option value="showtimerrule">showtimerrule</option>
<option value="leftclick">leftclick</option>
<option value="rightclick">rightclick</option>
<option value="middleclick">middleclick</option>
<option value="doubleclick">doubleclick</option>
<option value="longclick">longclick</option>
<option value="wheelup">wheelup</option>
<option value="wheeldown">wheeldown</option>
<option value="mousedrag">mousedrag</option>
<option value="mousemove">mousemove</option>
<option value="tap">tap</option>
<option value="longpress">longpress</option>
<option value="pangesture">pangesture</option>
<option value="zoomgesture">zoomgesture</option>
<option value="rotategesture">rotategesture</option>
<option value="swipeleft">swipeleft</option>
<option value="swiperight">swiperight</option>
<option value="swipeup">swipeup</option>
<option value="swipedown">swipedown</option>
<option value="error">error</option>
<option value="noop">noop</option>
</options>
</state>
</channel-type>
<channel-type id="systemcommand" advanced="true">
<item-type>String</item-type>
<label>Send System Command</label>
<description>Sends a system command to Kodi. This allows to shutdown/suspend/hibernate/reboot/quit Kodi</description>
<command>
<options>
<option value="Shutdown">Shutdown</option>
<option value="Suspend">Suspend</option>
<option value="Hibernate">Hibernate</option>
<option value="Reboot">Reboot</option>
<option value="Quit">Quit</option>
</options>
</command>
</channel-type>
<!-- Kodi variables -->
<channel-type id="pvr-channel" advanced="true">
<item-type>String</item-type>
<label>PVR Channel Title</label>
<description>Title of the current PVR channel</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="showtitle">
<item-type>String</item-type>
<label>Show Title</label>
<description>Title of the current show</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="album">
<item-type>String</item-type>
<label>Album</label>
<description>Album name of the current song</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="mediatype">
<item-type>String</item-type>
<label>Media Type</label>
<description>Media type of the current file</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="genreList">
<item-type>String</item-type>
<label>Genres</label>
<description>Comma-separated list of genres of the current file</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="thumbnail">
<item-type>Image</item-type>
<label>Thumbnail</label>
<description>The current thumbnail</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fanart">
<item-type>Image</item-type>
<label>Fanart</label>
<description>The current fanart</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="codec" advanced="true">
<item-type>String</item-type>
<label>Codec</label>
<description>Codec of currently playing media</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="currenttime">
<item-type>Number:Time</item-type>
<label>Current Time</label>
<description>Current time of currently playing media</description>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="currenttimepercentage" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Current Time in Percent</label>
<description>Current time of currently playing media</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>Length of currently playing media</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="id" advanced="true">
<item-type>Number</item-type>
<label>Id</label>
<description>id in kodi database</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="enabled">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>enabled</description>
</channel-type>
<channel-type id="channels" advanced="true">
<item-type>Number</item-type>
<label>Channels</label>
<description>Channels</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="width" advanced="true">
<item-type>Number</item-type>
<label>Video Width</label>
<description>Video Width</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="height" advanced="true">
<item-type>Number</item-type>
<label>Video Height</label>
<description>Video Height</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="index" advanced="true">
<item-type>Number</item-type>
<label>Index</label>
<description>stream index</description>
<state pattern="%d"/>
</channel-type>
<channel-type id="name" advanced="true">
<item-type>String</item-type>
<label>Name</label>
<description>Name</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="uniqueid" advanced="true">
<item-type>String</item-type>
<label>UniqueID</label>
<description>UniqueID</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="language" advanced="false">
<item-type>String</item-type>
<label>Language</label>
<description>stream Language</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="file" advanced="true">
<item-type>String</item-type>
<label>File</label>
<description>file path and name</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="rating" advanced="true">
<item-type>Number</item-type>
<label>Rating</label>
<description>Rating</description>
<state readOnly="true" pattern="%.1f"/>
</channel-type>
<channel-type id="mpaa" advanced="true">
<item-type>String</item-type>
<label>MPAA Rating</label>
<description>MPAA Rating</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="profile" advanced="true">
<item-type>String</item-type>
<label>Profile</label>
<description>Profile of Kodi</description>
<state pattern="%s"/>
</channel-type>
</thing:thing-descriptions>