added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.kaleidescape-${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-kaleidescape" description="Kaleidescape Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.kaleidescape/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link IKaleidescapeThingActions} defines the interface for all thing actions supported by the binding.
|
||||
* These methods, parameters, and return types are explained in {@link KaleidescapeThingActions}.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IKaleidescapeThingActions {
|
||||
|
||||
void sendKCommand(String kCommand);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeBindingConstants {
|
||||
public static final String BINDING_ID = "kaleidescape";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_PLAYER = new ThingTypeUID(BINDING_ID, "player");
|
||||
public static final ThingTypeUID THING_TYPE_CINEMA_ONE = new ThingTypeUID(BINDING_ID, "cinemaone");
|
||||
public static final ThingTypeUID THING_TYPE_ALTO = new ThingTypeUID(BINDING_ID, "alto");
|
||||
public static final ThingTypeUID THING_TYPE_STRATO = new ThingTypeUID(BINDING_ID, "strato");
|
||||
|
||||
public static final int DEFAULT_API_PORT = 10000;
|
||||
public static final int DISCOVERY_THREAD_POOL_SIZE = 15;
|
||||
public static final boolean DISCOVERY_DEFAULT_AUTO_DISCOVER = false;
|
||||
public static final int DISCOVERY_DEFAULT_TIMEOUT_RATE_MS = 500;
|
||||
public static final int DISCOVERY_DEFAULT_IP_TIMEOUT_RATE_MS = 750;
|
||||
|
||||
// List of all Channels
|
||||
public static final String POWER = "ui#power";
|
||||
public static final String VOLUME = "ui#volume";
|
||||
public static final String MUTE = "ui#mute";
|
||||
public static final String CONTROL = "ui#control";
|
||||
public static final String TITLE_NAME = "ui#title_name";
|
||||
public static final String PLAY_MODE = "ui#play_mode";
|
||||
public static final String PLAY_SPEED = "ui#play_speed";
|
||||
public static final String TITLE_NUM = "ui#title_num";
|
||||
public static final String TITLE_LENGTH = "ui#title_length";
|
||||
public static final String TITLE_LOC = "ui#title_loc";
|
||||
public static final String CHAPTER_NUM = "ui#chapter_num";
|
||||
public static final String CHAPTER_LENGTH = "ui#chapter_length";
|
||||
public static final String CHAPTER_LOC = "ui#chapter_loc";
|
||||
public static final String MOVIE_MEDIA_TYPE = "ui#movie_media_type";
|
||||
public static final String MOVIE_LOCATION = "ui#movie_location";
|
||||
public static final String ASPECT_RATIO = "ui#aspect_ratio";
|
||||
public static final String VIDEO_MODE = "ui#video_mode";
|
||||
public static final String VIDEO_MODE_COMPOSITE = "ui#video_mode_composite";
|
||||
public static final String VIDEO_MODE_COMPONENT = "ui#video_mode_component";
|
||||
public static final String VIDEO_MODE_HDMI = "ui#video_mode_hdmi";
|
||||
public static final String VIDEO_COLOR = "ui#video_color";
|
||||
public static final String VIDEO_COLOR_EOTF = "ui#video_color_eotf";
|
||||
public static final String CONTENT_COLOR = "ui#content_color";
|
||||
public static final String CONTENT_COLOR_EOTF = "ui#content_color_eotf";
|
||||
public static final String SCALE_MODE = "ui#scale_mode";
|
||||
public static final String SCREEN_MASK = "ui#screen_mask";
|
||||
public static final String SCREEN_MASK2 = "ui#screen_mask2";
|
||||
public static final String CINEMASCAPE_MASK = "ui#cinemascape_mask";
|
||||
public static final String CINEMASCAPE_MODE = "ui#cinemascape_mode";
|
||||
public static final String UI_STATE = "ui#ui_state";
|
||||
public static final String CHILD_MODE_STATE = "ui#child_mode_state";
|
||||
public static final String SYSTEM_READINESS_STATE = "ui#readiness_state";
|
||||
public static final String HIGHLIGHTED_SELECTION = "ui#highlighted_selection";
|
||||
public static final String USER_DEFINED_EVENT = "ui#user_defined_event";
|
||||
public static final String USER_INPUT = "ui#user_input";
|
||||
public static final String USER_INPUT_PROMPT = "ui#user_input_prompt";
|
||||
|
||||
public static final String MUSIC = "music#";
|
||||
public static final String MUSIC_CONTROL = "music#control";
|
||||
public static final String MUSIC_REPEAT = "music#repeat";
|
||||
public static final String MUSIC_RANDOM = "music#random";
|
||||
public static final String MUSIC_TRACK = "music#track";
|
||||
public static final String MUSIC_ARTIST = "music#artist";
|
||||
public static final String MUSIC_ALBUM = "music#album";
|
||||
public static final String MUSIC_PLAY_MODE = "music#play_mode";
|
||||
public static final String MUSIC_PLAY_SPEED = "music#play_speed";
|
||||
public static final String MUSIC_TRACK_LENGTH = "music#track_length";
|
||||
public static final String MUSIC_TRACK_POSITION = "music#track_position";
|
||||
public static final String MUSIC_TRACK_PROGRESS = "music#track_progress";
|
||||
public static final String MUSIC_TRACK_HANDLE = "music#track_handle";
|
||||
public static final String MUSIC_ALBUM_HANDLE = "music#album_handle";
|
||||
public static final String MUSIC_NOWPLAY_HANDLE = "music#nowplay_handle";
|
||||
|
||||
public static final String DETAIL = "detail#";
|
||||
|
||||
// metadata details - the values are keyed to what is sent by the component
|
||||
// prefaced with 'detail_' when updating the channel
|
||||
public static final String CONTENT_HANDLE = "content_handle";
|
||||
public static final String ALBUM_CONTENT_HANDLE = "album_content_handle";
|
||||
public static final String MOVIE = "movie";
|
||||
public static final String ALBUM = "album";
|
||||
public static final String DETAIL_TYPE = "type";
|
||||
public static final String DETAIL_TITLE = "title"; // movie
|
||||
public static final String DETAIL_ALBUM_TITLE = "album_title"; // album
|
||||
public static final String DETAIL_COVER_ART = "cover_art"; // both
|
||||
public static final String DETAIL_COVER_URL = "cover_url"; // both
|
||||
public static final String DETAIL_HIRES_COVER_URL = "hires_cover_url"; // both
|
||||
public static final String DETAIL_RATING = "rating"; // movie
|
||||
public static final String DETAIL_YEAR = "year"; // both
|
||||
public static final String DETAIL_RUNNING_TIME = "running_time"; // both
|
||||
public static final String DETAIL_ACTORS = "actors"; // movie
|
||||
public static final String DETAIL_ARTIST = "artist"; // album
|
||||
public static final String DETAIL_DIRECTORS = "directors"; // movie
|
||||
public static final String DETAIL_GENRES = "genres"; // both
|
||||
public static final String DETAIL_RATING_REASON = "rating_reason"; // movie
|
||||
public static final String DETAIL_SYNOPSIS = "synopsis"; // movie
|
||||
public static final String DETAIL_REVIEW = "review"; // album
|
||||
public static final String DETAIL_COLOR_DESCRIPTION = "color_description"; // movie
|
||||
public static final String DETAIL_COUNTRY = "country"; // movie
|
||||
public static final String DETAIL_ASPECT_RATIO = "aspect_ratio"; // movie
|
||||
public static final String DETAIL_DISC_LOCATION = "disc_location"; // both
|
||||
|
||||
// make a list of all allowed metatdata channels,
|
||||
// used to filter out what we don't want from the component
|
||||
public static final Set<String> METADATA_CHANNELS = new HashSet<String>(
|
||||
Arrays.asList(DETAIL_TITLE, DETAIL_ALBUM_TITLE, DETAIL_COVER_URL, DETAIL_HIRES_COVER_URL, DETAIL_RATING,
|
||||
DETAIL_YEAR, DETAIL_RUNNING_TIME, DETAIL_ACTORS, DETAIL_ARTIST, DETAIL_DIRECTORS, DETAIL_GENRES,
|
||||
DETAIL_RATING_REASON, DETAIL_SYNOPSIS, DETAIL_REVIEW, DETAIL_COLOR_DESCRIPTION, DETAIL_COUNTRY,
|
||||
DETAIL_ASPECT_RATIO, DETAIL_DISC_LOCATION));
|
||||
|
||||
public static final String STANDBY_MSG = "Device is in standby";
|
||||
public static final String PROPERTY_COMPONENT_TYPE = "Component Type";
|
||||
public static final String PROPERTY_FRIENDLY_NAME = "Friendly Name";
|
||||
public static final String PROPERTY_SERIAL_NUMBER = "Serial Number";
|
||||
public static final String PROPERTY_CONTROL_PROTOCOL_ID = "Control Protocol ID";
|
||||
public static final String PROPERTY_SYSTEM_VERSION = "System Version";
|
||||
public static final String PROPERTY_PROTOCOL_VERSION = "Protocol Version";
|
||||
|
||||
public static final String GET_DEVICE_TYPE_NAME = "GET_DEVICE_TYPE_NAME";
|
||||
public static final String GET_FRIENDLY_NAME = "GET_FRIENDLY_NAME";
|
||||
public static final String GET_DEVICE_INFO = "GET_DEVICE_INFO";
|
||||
public static final String GET_SYSTEM_VERSION = "GET_SYSTEM_VERSION";
|
||||
public static final String GET_DEVICE_POWER_STATE = "GET_DEVICE_POWER_STATE";
|
||||
public static final String GET_CINEMASCAPE_MASK = "GET_CINEMASCAPE_MASK";
|
||||
public static final String GET_CINEMASCAPE_MODE = "GET_CINEMASCAPE_MODE";
|
||||
public static final String GET_SCALE_MODE = "GET_SCALE_MODE";
|
||||
public static final String GET_SCREEN_MASK = "GET_SCREEN_MASK";
|
||||
public static final String GET_SCREEN_MASK2 = "GET_SCREEN_MASK2";
|
||||
public static final String GET_VIDEO_MODE = "GET_VIDEO_MODE";
|
||||
public static final String GET_UI_STATE = "GET_UI_STATE";
|
||||
public static final String GET_HIGHLIGHTED_SELECTION = "GET_HIGHLIGHTED_SELECTION";
|
||||
public static final String GET_CHILD_MODE_STATE = "GET_CHILD_MODE_STATE";
|
||||
public static final String GET_MOVIE_LOCATION = "GET_MOVIE_LOCATION";
|
||||
public static final String GET_MOVIE_MEDIA_TYPE = "GET_MOVIE_MEDIA_TYPE";
|
||||
public static final String GET_PLAYING_TITLE_NAME = "GET_PLAYING_TITLE_NAME";
|
||||
public static final String GET_PLAY_STATUS = "GET_PLAY_STATUS";
|
||||
public static final String GET_MUSIC_NOW_PLAYING_STATUS = "GET_MUSIC_NOW_PLAYING_STATUS";
|
||||
public static final String GET_MUSIC_PLAY_STATUS = "GET_MUSIC_PLAY_STATUS";
|
||||
public static final String GET_MUSIC_TITLE = "GET_MUSIC_TITLE";
|
||||
public static final String GET_SYSTEM_READINESS_STATE = "GET_SYSTEM_READINESS_STATE";
|
||||
public static final String GET_VIDEO_COLOR = "GET_VIDEO_COLOR";
|
||||
public static final String GET_CONTENT_COLOR = "GET_CONTENT_COLOR";
|
||||
public static final String SET_STATUS_CUE_PERIOD_1 = "SET_STATUS_CUE_PERIOD:1";
|
||||
public static final String GET_TIME = "GET_TIME";
|
||||
|
||||
public static final String LEAVE_STANDBY = "LEAVE_STANDBY";
|
||||
public static final String ENTER_STANDBY = "ENTER_STANDBY";
|
||||
|
||||
public static final String PLAY = "PLAY";
|
||||
public static final String PAUSE = "PAUSE";
|
||||
public static final String NEXT = "NEXT";
|
||||
public static final String PREVIOUS = "PREVIOUS";
|
||||
public static final String SCAN_FORWARD = "SCAN_FORWARD";
|
||||
public static final String SCAN_REVERSE = "SCAN_REVERSE";
|
||||
|
||||
public static final String MUSIC_REPEAT_ON = "MUSIC_REPEAT_ON";
|
||||
public static final String MUSIC_REPEAT_OFF = "MUSIC_REPEAT_OFF";
|
||||
public static final String MUSIC_RANDOM_ON = "MUSIC_RANDOM_ON";
|
||||
public static final String MUSIC_RANDOM_OFF = "MUSIC_RANDOM_OFF";
|
||||
|
||||
public static final String SEND_EVENT_VOLUME_CAPABILITIES_15 = "SEND_EVENT:VOLUME_CAPABILITIES=15";
|
||||
public static final String SEND_EVENT_VOLUME_LEVEL_EQ = "SEND_EVENT:VOLUME_LEVEL=";
|
||||
public static final String SEND_EVENT_MUTE = "SEND_EVENT:MUTE_";
|
||||
public static final String MUTE_ON = "ON_FB";
|
||||
public static final String MUTE_OFF = "OFF_FB";
|
||||
|
||||
public static final String ONE = "1";
|
||||
public static final String ZERO = "0";
|
||||
public static final String EMPTY = "";
|
||||
}
|
||||
@@ -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.kaleidescape.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeException} class is used for any exception thrown by the binding
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public KaleidescapeException() {
|
||||
}
|
||||
|
||||
public KaleidescapeException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
public KaleidescapeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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.kaleidescape.internal;
|
||||
|
||||
import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.kaleidescape.internal.handler.KaleidescapeHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.kaleidescape", service = ThingHandlerFactory.class)
|
||||
public class KaleidescapeHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_PLAYER, THING_TYPE_CINEMA_ONE, THING_TYPE_ALTO, THING_TYPE_STRATO)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public KaleidescapeHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference SerialPortManager serialPortManager) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@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 (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new KaleidescapeHandler(thing, serialPortManager, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.kaleidescape.internal.handler.KaleidescapeHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Some automation actions to be used with a {@link KaleidescapeThingActions}
|
||||
*
|
||||
* @author Michael Lobstein - initial contribution
|
||||
*
|
||||
*/
|
||||
@ThingActionsScope(name = "kaleidescape")
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeThingActions implements ThingActions, IKaleidescapeThingActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeThingActions.class);
|
||||
|
||||
private @Nullable KaleidescapeHandler handler;
|
||||
|
||||
@RuleAction(label = "sendKCommand", description = "Action that sends raw command to the kaleidescape zone")
|
||||
public void sendKCommand(@ActionInput(name = "sendKCommand") String kCommand) {
|
||||
KaleidescapeHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
localHandler.handleRawCommand(kCommand);
|
||||
logger.debug("sendKCommand called with command: {}", kCommand);
|
||||
} else {
|
||||
logger.warn("unable to send command, KaleidescapeHandler was null");
|
||||
}
|
||||
}
|
||||
|
||||
/** Static alias to support the old DSL rules engine and make the action available there. */
|
||||
public static void sendKCommand(@Nullable ThingActions actions, String kCommand) throws IllegalArgumentException {
|
||||
invokeMethodOf(actions).sendKCommand(kCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (KaleidescapeHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
private static IKaleidescapeThingActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(KaleidescapeThingActions.class.getName())) {
|
||||
if (actions instanceof KaleidescapeThingActions) {
|
||||
return (IKaleidescapeThingActions) actions;
|
||||
} else {
|
||||
return (IKaleidescapeThingActions) Proxy.newProxyInstance(
|
||||
IKaleidescapeThingActions.class.getClassLoader(),
|
||||
new Class[] { IKaleidescapeThingActions.class },
|
||||
(Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of KaleidescapeThingActions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal.communication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract class for communicating with the Kaleidescape component
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class KaleidescapeConnector {
|
||||
private static final String SUCCESS_MSG = "01/1/000:/89";
|
||||
private static final String BEGIN_CMD = "01/1/";
|
||||
private static final String END_CMD = ":\r";
|
||||
|
||||
private final Pattern pattern = Pattern.compile("^(\\d{2})/./(\\d{3})\\:([^:^/]*)\\:(.*?)\\:/(\\d{2})$");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeConnector.class);
|
||||
|
||||
/** The output stream */
|
||||
protected @Nullable OutputStream dataOut;
|
||||
|
||||
/** The input stream */
|
||||
protected @Nullable InputStream dataIn;
|
||||
|
||||
/** true if the connection is established, false if not */
|
||||
private boolean connected;
|
||||
|
||||
private @Nullable Thread readerThread;
|
||||
|
||||
private final List<KaleidescapeMessageEventListener> listeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Get whether the connection is established or not
|
||||
*
|
||||
* @return true if the connection is established
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the connection is established or not
|
||||
*
|
||||
* @param connected true if the connection is established
|
||||
*/
|
||||
protected void setConnected(boolean connected) {
|
||||
this.connected = connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thread that handles the feedback messages
|
||||
*
|
||||
* @param readerThread the thread
|
||||
*/
|
||||
protected void setReaderThread(Thread readerThread) {
|
||||
this.readerThread = readerThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the connection with the Kaleidescape component
|
||||
*
|
||||
* @throws KaleidescapeException - In case of any problem
|
||||
*/
|
||||
public abstract void open() throws KaleidescapeException;
|
||||
|
||||
/**
|
||||
* Close the connection with the Kaleidescape component
|
||||
*/
|
||||
public abstract void close();
|
||||
|
||||
/**
|
||||
* Stop the thread that handles the feedback messages and close the opened input and output streams
|
||||
*/
|
||||
protected void cleanup() {
|
||||
Thread readerThread = this.readerThread;
|
||||
OutputStream dataOut = this.dataOut;
|
||||
if (dataOut != null) {
|
||||
try {
|
||||
dataOut.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing dataOut: {}", e.getMessage());
|
||||
}
|
||||
this.dataOut = null;
|
||||
}
|
||||
InputStream dataIn = this.dataIn;
|
||||
if (dataIn != null) {
|
||||
try {
|
||||
dataIn.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing dataIn: {}", e.getMessage());
|
||||
}
|
||||
this.dataIn = null;
|
||||
}
|
||||
if (readerThread != null) {
|
||||
readerThread.interrupt();
|
||||
this.readerThread = null;
|
||||
try {
|
||||
readerThread.join(3000);
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn("Error joining readerThread: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes
|
||||
* actually read is returned as an integer.
|
||||
*
|
||||
* @param dataBuffer the buffer into which the data is read.
|
||||
*
|
||||
* @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the
|
||||
* stream has been reached.
|
||||
*
|
||||
* @throws KaleidescapeException - If the input stream is null, if the first byte cannot be read for any reason
|
||||
* other than the end of the file, if the input stream has been closed, or if some other I/O error
|
||||
* occurs.
|
||||
*/
|
||||
protected int readInput(byte[] dataBuffer) throws KaleidescapeException {
|
||||
InputStream dataIn = this.dataIn;
|
||||
if (dataIn == null) {
|
||||
throw new KaleidescapeException("readInput failed: input stream is null");
|
||||
}
|
||||
try {
|
||||
return dataIn.read(dataBuffer);
|
||||
} catch (IOException e) {
|
||||
throw new KaleidescapeException("readInput failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the connection by requesting the time from the component
|
||||
*
|
||||
* @throws KaleidescapeException - In case of any problem
|
||||
*/
|
||||
public void ping() throws KaleidescapeException {
|
||||
sendCommand(KaleidescapeBindingConstants.GET_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the Kaleidescape component to execute a command
|
||||
*
|
||||
* @param cmd the command to execute
|
||||
*
|
||||
* @throws KaleidescapeException - In case of any problem
|
||||
*/
|
||||
public void sendCommand(@Nullable String cmd) throws KaleidescapeException {
|
||||
sendCommand(cmd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the Kaleidescape component to execute a command
|
||||
*
|
||||
* @param cmd the command to execute
|
||||
* @param cachedMessage an optional cached message that will immediately be sent as a KaleidescapeMessageEvent
|
||||
*
|
||||
* @throws KaleidescapeException - In case of any problem
|
||||
*/
|
||||
public void sendCommand(@Nullable String cmd, @Nullable String cachedMessage) throws KaleidescapeException {
|
||||
// if sent a cachedMessage, just send out an event with the data so KaleidescapeMessageHandler will process it
|
||||
if (cachedMessage != null) {
|
||||
logger.debug("Command: '{}' returning cached value: '{}'", cmd, cachedMessage);
|
||||
// change GET_SOMETHING into SOMETHING and special case GET_PLAYING_TITLE_NAME into TITLE_NAME
|
||||
dispatchKeyValue(cmd.replace("GET_", "").replace("PLAYING_TITLE_NAME", "TITLE_NAME"), cachedMessage, true);
|
||||
return;
|
||||
}
|
||||
|
||||
String messageStr = BEGIN_CMD + cmd + END_CMD;
|
||||
|
||||
logger.debug("Send command {}", messageStr);
|
||||
|
||||
OutputStream dataOut = this.dataOut;
|
||||
if (dataOut == null) {
|
||||
throw new KaleidescapeException("Send command \"" + messageStr + "\" failed: output stream is null");
|
||||
}
|
||||
try {
|
||||
dataOut.write(messageStr.getBytes(StandardCharsets.US_ASCII));
|
||||
dataOut.flush();
|
||||
} catch (IOException e) {
|
||||
throw new KaleidescapeException("Send command \"" + cmd + "\" failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener to the list of listeners to be notified with events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void addEventListener(KaleidescapeMessageEventListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener from the list of listeners to be notified with events
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void removeEventListener(KaleidescapeMessageEventListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze an incoming message and dispatch corresponding event (key, value) to the event listeners
|
||||
*
|
||||
* @param incomingMessage the received message
|
||||
*/
|
||||
public void handleIncomingMessage(byte[] incomingMessage) {
|
||||
String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim();
|
||||
|
||||
// ignore empty success messages
|
||||
if (!SUCCESS_MSG.equals(message)) {
|
||||
logger.debug("handleIncomingMessage: {}", message);
|
||||
|
||||
// Kaleidescape message ie: 01/!/000:TITLE_NAME:Office Space:/79
|
||||
// or: 01/!/000:PLAY_STATUS:2:0:01:07124:00138:001:00311:00138:/27
|
||||
// or: 01/1/000:TIME:2020:04:27:11:38:52:CDT:/84
|
||||
// g1=zoneid, g2=sequence, g3=message name, g4=message, g5=checksum
|
||||
// pattern : "^(\\d{2})/./(\\d{3})\\:([^:^/]*)\\:(.*?)\\:/(\\d{2})$");
|
||||
|
||||
Matcher matcher = pattern.matcher(message);
|
||||
if (matcher.find()) {
|
||||
dispatchKeyValue(matcher.group(3), matcher.group(4), false);
|
||||
} else {
|
||||
logger.debug("no match on message: {}", message);
|
||||
if (message.contains(KaleidescapeBindingConstants.STANDBY_MSG)) {
|
||||
dispatchKeyValue(KaleidescapeBindingConstants.STANDBY_MSG, "", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an event (key, value, isCached) to the event listeners
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @param isCached indicates if this event was generated from a cached value
|
||||
*/
|
||||
private void dispatchKeyValue(String key, String value, boolean isCached) {
|
||||
KaleidescapeMessageEvent event = new KaleidescapeMessageEvent(this, key, value, isCached);
|
||||
listeners.forEach(l -> l.onNewMessageEvent(event));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.communication;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class to create a default Kaleidescape before initialization is complete.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeDefaultConnector extends KaleidescapeConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeDefaultConnector.class);
|
||||
|
||||
@Override
|
||||
public void open() throws KaleidescapeException {
|
||||
logger.warn("Kaleidescape binding incorrectly configured. Please configure for IP or serial connection");
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendCommand(@Nullable String value) {
|
||||
logger.warn("Kaleidescape binding incorrectly configured. Please configure for IP or serial connection");
|
||||
setConnected(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal.communication;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeFormatter} is a utility class with formatting methods for Kaleidescape strings
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeFormatter {
|
||||
public static String formatString(String input) {
|
||||
if (!input.equals("")) {
|
||||
// convert || back to :
|
||||
input = input.replace("||", ":");
|
||||
|
||||
// if input does not have any escaped characters, bypass all the replace()'s
|
||||
if (input.contains("\\")) {
|
||||
// fix escaped :
|
||||
input = input.replace("\\:", ":");
|
||||
|
||||
// fix escaped /
|
||||
input = input.replace("\\/", "/");
|
||||
|
||||
// convert \r into comma space
|
||||
input = input.replace("\\r", ", ");
|
||||
|
||||
// convert \d146 from review text into apostrophe
|
||||
input = input.replace("\\d146", "'");
|
||||
// convert \d147 & \d148 from review text into double quote
|
||||
input = input.replace("\\d147", "\"");
|
||||
input = input.replace("\\d148", "\"");
|
||||
|
||||
// fix the encoding for k mangled extended ascii characters (chars coming in as \dnnn)
|
||||
// I.e. characters with accent, umlaut, etc., they need to be restored to the correct character
|
||||
// example: Noel (with umlaut 'o') comes in as N\d246el
|
||||
input = input.replaceAll("(?i)\\\\d([0-9]{3})", "\\&#$1;"); // first convert to html escaped codes
|
||||
// then convert with unescapeHtml, not sure how to do this without the Apache libraries :(
|
||||
return StringEscapeUtils.unescapeHtml(input);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.communication;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class for communicating with the Kaleidescape component through a IP connection or a serial over IP connection
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeIpConnector extends KaleidescapeConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeIpConnector.class);
|
||||
|
||||
private final @Nullable String address;
|
||||
private final int port;
|
||||
private final String uid;
|
||||
|
||||
private @Nullable Socket clientSocket;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param address the IP address of the Kaleidescape component
|
||||
* @param port the TCP port to be used
|
||||
*/
|
||||
public KaleidescapeIpConnector(@Nullable String address, int port, String uid) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void open() throws KaleidescapeException {
|
||||
logger.debug("Opening IP connection on IP {} port {}", this.address, this.port);
|
||||
try {
|
||||
Socket clientSocket = new Socket(this.address, this.port);
|
||||
clientSocket.setSoTimeout(100);
|
||||
|
||||
dataOut = new DataOutputStream(clientSocket.getOutputStream());
|
||||
dataIn = new DataInputStream(clientSocket.getInputStream());
|
||||
|
||||
Thread thread = new KaleidescapeReaderThread(this, this.uid, this.address + "." + this.port);
|
||||
setReaderThread(thread);
|
||||
thread.start();
|
||||
|
||||
this.clientSocket = clientSocket;
|
||||
|
||||
setConnected(true);
|
||||
|
||||
logger.debug("IP connection opened");
|
||||
} catch (IOException | SecurityException | IllegalArgumentException e) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening IP connection failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
logger.debug("Closing IP connection");
|
||||
super.cleanup();
|
||||
Socket clientSocket = this.clientSocket;
|
||||
if (clientSocket != null) {
|
||||
try {
|
||||
clientSocket.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
this.clientSocket = null;
|
||||
}
|
||||
setConnected(false);
|
||||
logger.debug("IP connection closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes
|
||||
* actually read is returned as an integer.
|
||||
* In case of socket timeout, the returned value is 0.
|
||||
*
|
||||
* @param dataBuffer the buffer into which the data is read.
|
||||
*
|
||||
* @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of the
|
||||
* stream has been reached.
|
||||
*
|
||||
* @throws KaleidescapeException - If the input stream is null, if the first byte cannot be read for any reason
|
||||
* other than the end of the file, if the input stream has been closed, or if some other I/O error
|
||||
* occurs.
|
||||
*/
|
||||
@Override
|
||||
protected int readInput(byte[] dataBuffer) throws KaleidescapeException {
|
||||
InputStream dataIn = this.dataIn;
|
||||
if (dataIn == null) {
|
||||
throw new KaleidescapeException("readInput failed: input stream is null");
|
||||
}
|
||||
try {
|
||||
return dataIn.read(dataBuffer);
|
||||
} catch (SocketTimeoutException e) {
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
throw new KaleidescapeException("readInput failed: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.communication;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* KaleidescapeMessageEvent used to notify changes coming from messages received from the Kaleidescape component
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeMessageEvent extends EventObject {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String key;
|
||||
private final String value;
|
||||
private final boolean isCached;
|
||||
|
||||
public KaleidescapeMessageEvent(Object source, String key, String value, boolean isCached) {
|
||||
super(source);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.isCached = isCached;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return isCached;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal.communication;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Kaleidescape Event Listener interface. Handles incoming Kaleidescape message events
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface KaleidescapeMessageEventListener extends EventListener {
|
||||
/**
|
||||
* Event handler method for incoming Kaleidescape message events
|
||||
*
|
||||
* @param event the KaleidescapeMessageEvent object
|
||||
*/
|
||||
void onNewMessageEvent(KaleidescapeMessageEvent event);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.communication;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A class that reads messages from the Kaleidescape component in a dedicated thread
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeReaderThread extends Thread {
|
||||
private static final int READ_BUFFER_SIZE = 16;
|
||||
private static final int SIZE = 512;
|
||||
private static final char TERM_CHAR = '\r';
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeReaderThread.class);
|
||||
|
||||
private KaleidescapeConnector connector;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param connector the object that should handle the received message
|
||||
* @param uid the thing uid string
|
||||
* @param connectionId a string that uniquely identifies the particular connection
|
||||
*/
|
||||
public KaleidescapeReaderThread(KaleidescapeConnector connector, String uid, String connectionId) {
|
||||
super("OH-binding-" + uid + "-" + connectionId);
|
||||
this.connector = connector;
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("Data listener started");
|
||||
|
||||
byte[] readDataBuffer = new byte[READ_BUFFER_SIZE];
|
||||
byte[] dataBuffer = new byte[SIZE];
|
||||
int index = 0;
|
||||
|
||||
try {
|
||||
while (!Thread.interrupted()) {
|
||||
int len = connector.readInput(readDataBuffer);
|
||||
if (len > 0) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
|
||||
if (index < SIZE) {
|
||||
dataBuffer[index++] = readDataBuffer[i];
|
||||
}
|
||||
if (readDataBuffer[i] == TERM_CHAR) {
|
||||
if (index >= SIZE) {
|
||||
dataBuffer[index - 1] = (byte) TERM_CHAR;
|
||||
}
|
||||
byte[] msg = Arrays.copyOf(dataBuffer, index);
|
||||
connector.handleIncomingMessage(msg);
|
||||
index = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("Reading failed: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
logger.debug("Data listener stopped");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal.communication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class for communicating with the Kaleidescape component through a serial connection
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeSerialConnector extends KaleidescapeConnector {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeSerialConnector.class);
|
||||
|
||||
private final String serialPortName;
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final String uid;
|
||||
|
||||
private @Nullable SerialPort serialPort;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param serialPortManager the serial port manager
|
||||
* @param serialPortName the serial port name to be used
|
||||
* @param uid the thing uid string
|
||||
*/
|
||||
public KaleidescapeSerialConnector(SerialPortManager serialPortManager, String serialPortName, String uid) {
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.serialPortName = serialPortName;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void open() throws KaleidescapeException {
|
||||
logger.debug("Opening serial connection on port {}", serialPortName);
|
||||
try {
|
||||
SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
|
||||
if (portIdentifier == null) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening serial connection failed: No Such Port");
|
||||
}
|
||||
|
||||
SerialPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
|
||||
|
||||
commPort.setSerialPortParams(19200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
|
||||
commPort.enableReceiveThreshold(1);
|
||||
commPort.enableReceiveTimeout(100);
|
||||
commPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
|
||||
|
||||
InputStream dataIn = commPort.getInputStream();
|
||||
OutputStream dataOut = commPort.getOutputStream();
|
||||
|
||||
if (dataOut != null) {
|
||||
dataOut.flush();
|
||||
}
|
||||
if (dataIn != null && dataIn.markSupported()) {
|
||||
try {
|
||||
dataIn.reset();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Caught IOException at dataIn.reset(): {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Thread thread = new KaleidescapeReaderThread(this, this.uid, this.serialPortName);
|
||||
setReaderThread(thread);
|
||||
thread.start();
|
||||
|
||||
this.serialPort = commPort;
|
||||
this.dataIn = dataIn;
|
||||
this.dataOut = dataOut;
|
||||
|
||||
setConnected(true);
|
||||
|
||||
logger.debug("Serial connection opened");
|
||||
} catch (PortInUseException e) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening serial connection failed: Port in Use Exception", e);
|
||||
} catch (UnsupportedCommOperationException e) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening serial connection failed: Unsupported Comm Operation Exception",
|
||||
e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening serial connection failed: Unsupported Encoding Exception", e);
|
||||
} catch (IOException e) {
|
||||
setConnected(false);
|
||||
throw new KaleidescapeException("Opening serial connection failed: IO Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
logger.debug("Closing serial connection");
|
||||
SerialPort serialPort = this.serialPort;
|
||||
if (serialPort != null) {
|
||||
serialPort.removeEventListener();
|
||||
}
|
||||
super.cleanup();
|
||||
if (serialPort != null) {
|
||||
serialPort.close();
|
||||
this.serialPort = null;
|
||||
}
|
||||
setConnected(false);
|
||||
logger.debug("Serial connection closed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.communication;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Provides mapping of various Kaleidescape status codes to plain language meanings
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeStatusCodes {
|
||||
private static final String UNUSED = "unused";
|
||||
private static final String UNKNOWN = "unknown";
|
||||
private static final String RESERVED = "reserved";
|
||||
|
||||
// map to lookup play mode
|
||||
public static final Map<String, String> PLAY_MODE = new HashMap<>();
|
||||
static {
|
||||
PLAY_MODE.put("0", "Nothing playing");
|
||||
PLAY_MODE.put("1", "Paused");
|
||||
PLAY_MODE.put("2", "Playing");
|
||||
PLAY_MODE.put("3", UNUSED);
|
||||
PLAY_MODE.put("4", "Forward scan");
|
||||
PLAY_MODE.put("5", UNUSED);
|
||||
PLAY_MODE.put("6", "Reverse scan");
|
||||
}
|
||||
|
||||
// map to lookup media type
|
||||
public static final Map<String, String> MEDIA_TYPE = new HashMap<>();
|
||||
static {
|
||||
MEDIA_TYPE.put("00", "Nothing playing");
|
||||
MEDIA_TYPE.put("01", "DVD");
|
||||
MEDIA_TYPE.put("02", "Video stream");
|
||||
MEDIA_TYPE.put("03", "Blu-ray Disc");
|
||||
}
|
||||
|
||||
// map to lookup movie location
|
||||
public static final Map<String, String> MOVIE_LOCATION = new HashMap<>();
|
||||
static {
|
||||
MOVIE_LOCATION.put("00", UNKNOWN);
|
||||
MOVIE_LOCATION.put("01", UNUSED);
|
||||
MOVIE_LOCATION.put("02", UNUSED);
|
||||
MOVIE_LOCATION.put("03", "Main content");
|
||||
MOVIE_LOCATION.put("04", "Intermission");
|
||||
MOVIE_LOCATION.put("05", "End Credits");
|
||||
MOVIE_LOCATION.put("06", "DVD/Blu-ray Disc Menu");
|
||||
}
|
||||
|
||||
// map to lookup aspect ratio
|
||||
public static final Map<String, String> ASPECT_RATIO = new HashMap<>();
|
||||
static {
|
||||
ASPECT_RATIO.put("00", UNKNOWN);
|
||||
ASPECT_RATIO.put("01", "1.33");
|
||||
ASPECT_RATIO.put("02", "1.66");
|
||||
ASPECT_RATIO.put("03", "1.78");
|
||||
ASPECT_RATIO.put("04", "1.85");
|
||||
ASPECT_RATIO.put("05", "2.35");
|
||||
}
|
||||
|
||||
public static final Map<String, String> VIDEO_MODE = new HashMap<>();
|
||||
|
||||
static {
|
||||
VIDEO_MODE.put("00", "No output");
|
||||
VIDEO_MODE.put("01", "480i60 4:3");
|
||||
VIDEO_MODE.put("02", "480i60 16:9");
|
||||
VIDEO_MODE.put("03", "480p60 4:3");
|
||||
VIDEO_MODE.put("04", "480p60 16:9");
|
||||
VIDEO_MODE.put("05", "576i50 4:3");
|
||||
VIDEO_MODE.put("06", "576i50 16:9");
|
||||
VIDEO_MODE.put("07", "576p50 4:3");
|
||||
VIDEO_MODE.put("08", "576p50 16:9");
|
||||
VIDEO_MODE.put("09", "720p60 NTSC HD");
|
||||
VIDEO_MODE.put("10", "720p50 PAL HD");
|
||||
VIDEO_MODE.put("11", "1080i60 16:9");
|
||||
VIDEO_MODE.put("12", "1080i50 16:9");
|
||||
VIDEO_MODE.put("13", "1080p60 16:9");
|
||||
VIDEO_MODE.put("14", "1080p50 16:9");
|
||||
VIDEO_MODE.put("15", RESERVED);
|
||||
VIDEO_MODE.put("16", RESERVED);
|
||||
VIDEO_MODE.put("17", "1080p24 16:9");
|
||||
VIDEO_MODE.put("18", RESERVED);
|
||||
VIDEO_MODE.put("19", "480i60 64:27");
|
||||
VIDEO_MODE.put("20", "576i50 64:27");
|
||||
VIDEO_MODE.put("21", "1080i60 64:27");
|
||||
VIDEO_MODE.put("22", "1080i50 64:27");
|
||||
VIDEO_MODE.put("23", "1080p60 64:27");
|
||||
VIDEO_MODE.put("24", "1080p50 64:27");
|
||||
VIDEO_MODE.put("25", "1080p24 64:27");
|
||||
VIDEO_MODE.put("26", "1080p24 64:27");
|
||||
VIDEO_MODE.put("27", "3840x 2160p24 16:9");
|
||||
VIDEO_MODE.put("28", "3840x 2160p24 64:27");
|
||||
VIDEO_MODE.put("29", "3840x 2160p30 16:9");
|
||||
VIDEO_MODE.put("30", "3840x 2160p30 64:27");
|
||||
VIDEO_MODE.put("31", "3840x 2160p60 16:9");
|
||||
VIDEO_MODE.put("32", "3840x 2160p60 64:27");
|
||||
VIDEO_MODE.put("33", "3840x 2160p25 16:9");
|
||||
VIDEO_MODE.put("34", "3840x 2160p25 64:27");
|
||||
VIDEO_MODE.put("35", "3840x 2160p50 16:9");
|
||||
VIDEO_MODE.put("36", "3840x 2160p50 64:27");
|
||||
VIDEO_MODE.put("37", "3840x 2160p24 16:9");
|
||||
VIDEO_MODE.put("38", "3840x 2160p24 64:27");
|
||||
}
|
||||
|
||||
// map to lookup eotf
|
||||
public static final Map<String, String> EOTF = new HashMap<>();
|
||||
static {
|
||||
EOTF.put("00", UNKNOWN);
|
||||
EOTF.put("01", "SDR");
|
||||
EOTF.put("02", "HDR");
|
||||
EOTF.put("03", "SMTPE ST 2048");
|
||||
}
|
||||
|
||||
// map to lookup readiness state
|
||||
public static final Map<String, String> READINESS_STATE = new HashMap<>();
|
||||
static {
|
||||
READINESS_STATE.put("0", "system is ready");
|
||||
READINESS_STATE.put("1", "system is becoming ready");
|
||||
READINESS_STATE.put("2", "system is idle");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.kaleidescape.internal.configuration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeThingConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeThingConfiguration {
|
||||
public @Nullable String serialPort;
|
||||
public @Nullable String host;
|
||||
public @Nullable Integer port;
|
||||
public @Nullable Integer updatePeriod;
|
||||
public boolean volumeEnabled;
|
||||
public Integer initialVolume = 0;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeDiscoveryJob} class allow manual discovery of
|
||||
* Kaleidescape components for a single IP address. This is used
|
||||
* for threading to make discovery faster.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeDiscoveryJob implements Runnable {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeDiscoveryJob.class);
|
||||
|
||||
// Component Types
|
||||
private static final String PLAYER = "Player";
|
||||
private static final String CINEMA_ONE = "Cinema One";
|
||||
private static final String ALTO = "Alto";
|
||||
private static final String STRATO = "Strato";
|
||||
private static final String STRATO_S = "Strato S";
|
||||
private static final String DISC_VAULT = "Disc Vault";
|
||||
|
||||
private static final Set<String> ALLOWED_DEVICES = new HashSet<String>(
|
||||
Arrays.asList(PLAYER, CINEMA_ONE, ALTO, STRATO, STRATO_S, DISC_VAULT));
|
||||
|
||||
private KaleidescapeDiscoveryService discoveryClass;
|
||||
|
||||
private ThingTypeUID thingTypeUid = THING_TYPE_PLAYER;
|
||||
private String ipAddress = EMPTY;
|
||||
private String friendlyName = EMPTY;
|
||||
private String serialNumber = EMPTY;
|
||||
|
||||
public KaleidescapeDiscoveryJob(KaleidescapeDiscoveryService service, String ip) {
|
||||
this.discoveryClass = service;
|
||||
this.ipAddress = ip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (hasKaleidescapeDevice(this.ipAddress)) {
|
||||
discoveryClass.submitDiscoveryResults(this.thingTypeUid, this.ipAddress, this.friendlyName,
|
||||
this.serialNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a Kaleidescape component with a movie player zone is available at a given IP address.
|
||||
*
|
||||
* @param ip IP address of the Kaleidescape component as a string.
|
||||
* @return True if a component is found, false if not.
|
||||
*/
|
||||
private boolean hasKaleidescapeDevice(String ip) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(ip);
|
||||
|
||||
if (isKaleidescapeDevice(address, DEFAULT_API_PORT)) {
|
||||
return true;
|
||||
} else {
|
||||
logger.debug("No Kaleidescape component found at IP address ({})", ip);
|
||||
return false;
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
logger.debug("Unknown host: {} - {}", ip, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to establish a connection to a hostname and port and then interrogate the component
|
||||
*
|
||||
* @param host Hostname or IP address to connect to.
|
||||
* @param port Port to attempt to connect to.
|
||||
* @return True if the component found is one the binding supports
|
||||
*/
|
||||
private boolean isKaleidescapeDevice(InetAddress host, int port) {
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(host, port), DISCOVERY_DEFAULT_IP_TIMEOUT_RATE_MS);
|
||||
|
||||
OutputStream output = socket.getOutputStream();
|
||||
PrintWriter writer = new PrintWriter(output, true);
|
||||
|
||||
// query the component to see if it has video zones, the device type, friendly name, and serial number
|
||||
writer.println("01/1/GET_NUM_ZONES:");
|
||||
writer.println("01/1/GET_DEVICE_TYPE_NAME:");
|
||||
writer.println("01/1/GET_FRIENDLY_NAME:");
|
||||
writer.println("01/1/GET_DEVICE_INFO:");
|
||||
|
||||
InputStream input = socket.getInputStream();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
|
||||
|
||||
String componentType = EMPTY;
|
||||
String line;
|
||||
String videoZone = null;
|
||||
String audioZone = null;
|
||||
int lineCount = 0;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String[] strArr = line.split(":");
|
||||
|
||||
if (strArr.length >= 4) {
|
||||
switch (strArr[1]) {
|
||||
case "NUM_ZONES":
|
||||
videoZone = strArr[2];
|
||||
audioZone = strArr[3];
|
||||
break;
|
||||
case "DEVICE_TYPE_NAME":
|
||||
componentType = strArr[2];
|
||||
break;
|
||||
case "FRIENDLY_NAME":
|
||||
friendlyName = strArr[2];
|
||||
break;
|
||||
case "DEVICE_INFO":
|
||||
serialNumber = strArr[3].trim(); // take off leading zeros
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.debug("isKaleidescapeDevice() - Unable to process line: {}", line);
|
||||
}
|
||||
|
||||
lineCount++;
|
||||
|
||||
// stop after reading four lines
|
||||
if (lineCount > 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we have a video zone
|
||||
if ("01".equals(videoZone)) {
|
||||
// now check if we are one of the allowed types
|
||||
if (ALLOWED_DEVICES.contains(componentType)) {
|
||||
if (STRATO_S.equals(componentType) || STRATO.equals(componentType)) {
|
||||
thingTypeUid = THING_TYPE_STRATO;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A 'Player' without an audio zone is really a Strato C
|
||||
// does not work yet, Strato C erroneously reports "01" for audio zones
|
||||
// so we are unable to differentiate a Strato C from a Premiere player
|
||||
if ("00".equals(audioZone) && PLAYER.equals(componentType)) {
|
||||
thingTypeUid = THING_TYPE_STRATO;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Alto
|
||||
if (ALTO.equals(componentType)) {
|
||||
thingTypeUid = THING_TYPE_ALTO;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cinema One
|
||||
if (CINEMA_ONE.equals(componentType)) {
|
||||
thingTypeUid = THING_TYPE_CINEMA_ONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A Disc Vault with a video zone (the M700 vault), just call it a THING_TYPE_PLAYER
|
||||
if (DISC_VAULT.equals(componentType)) {
|
||||
thingTypeUid = THING_TYPE_PLAYER;
|
||||
return true;
|
||||
}
|
||||
|
||||
// default returns THING_TYPE_PLAYER
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.debug("isKaleidescapeDevice() IOException: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.net.util.SubnetUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.NamedThreadFactory;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeDiscoveryService} class allow manual discovery of Kaleidescape components.
|
||||
*
|
||||
* @author Chris Graham - Initial contribution
|
||||
* @author Michael Lobstein - Adapted for the Kaleidescape binding
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, configurationPid = "discovery.kaleidescape")
|
||||
public class KaleidescapeDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeDiscoveryService.class);
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_PLAYER, THING_TYPE_CINEMA_ONE, THING_TYPE_ALTO, THING_TYPE_STRATO)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
@Activate
|
||||
public KaleidescapeDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, DISCOVERY_DEFAULT_TIMEOUT_RATE_MS, DISCOVERY_DEFAULT_AUTO_DISCOVER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting discovery of Kaleidescape components.");
|
||||
|
||||
try {
|
||||
List<String> ipList = getIpAddressScanList();
|
||||
|
||||
ExecutorService discoverySearchPool = Executors.newFixedThreadPool(DISCOVERY_THREAD_POOL_SIZE,
|
||||
new NamedThreadFactory("OH-binding-discovery.kaleidescape", true));
|
||||
|
||||
for (String ip : ipList) {
|
||||
discoverySearchPool.execute(new KaleidescapeDiscoveryJob(this, ip));
|
||||
}
|
||||
|
||||
discoverySearchPool.shutdown();
|
||||
} catch (Exception exp) {
|
||||
logger.debug("Kaleidescape discovery service encountered an error while scanning for components: {}",
|
||||
exp.getMessage());
|
||||
}
|
||||
|
||||
logger.debug("Completed discovery of Kaleidescape components.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Thing with an IP address and Component type given. Uses default port.
|
||||
*
|
||||
* @param thingTypeUid ThingTypeUID of detected Kaleidescape component.
|
||||
* @param ip IP address of the Kaleidescape component as a string.
|
||||
* @param friendlyName Name of Kaleidescape component as a string.
|
||||
* @param serialNumber Serial Number of Kaleidescape component as a string.
|
||||
*/
|
||||
public void submitDiscoveryResults(ThingTypeUID thingTypeUid, String ip, String friendlyName, String serialNumber) {
|
||||
ThingUID uid = new ThingUID(thingTypeUid, serialNumber);
|
||||
|
||||
HashMap<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put("host", ip);
|
||||
properties.put("port", DEFAULT_API_PORT);
|
||||
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withRepresentationProperty("host")
|
||||
.withLabel(friendlyName).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a string list of all the IP addresses associated with the network interfaces on
|
||||
* this machine.
|
||||
*
|
||||
* @return String list of IP addresses.
|
||||
* @throws UnknownHostException
|
||||
* @throws SocketException
|
||||
*/
|
||||
private List<String> getIpAddressScanList() throws UnknownHostException, SocketException {
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localHost);
|
||||
|
||||
for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
|
||||
InetAddress ipAddress = address.getAddress();
|
||||
|
||||
String cidrSubnet = ipAddress.getHostAddress() + "/" + address.getNetworkPrefixLength();
|
||||
|
||||
/* Apache Subnet Utils only supports IP v4 for creating string list of IP's */
|
||||
if (ipAddress instanceof Inet4Address) {
|
||||
logger.debug("Found interface IPv4 address to scan: {}", cidrSubnet);
|
||||
|
||||
SubnetUtils utils = new SubnetUtils(cidrSubnet);
|
||||
|
||||
results.addAll(Arrays.asList(utils.getInfo().getAllAddresses())); // not sure how to do this without the
|
||||
// Apache libraries
|
||||
} else if (ipAddress instanceof Inet6Address) {
|
||||
logger.debug("Found interface IPv6 address to scan: {}, ignoring", cidrSubnet);
|
||||
} else {
|
||||
logger.debug("Found interface unknown IP type address to scan: {}", cidrSubnet);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.handler;
|
||||
|
||||
import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeThingActions;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeConnector;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeDefaultConnector;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeIpConnector;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEvent;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEventListener;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeSerialConnector;
|
||||
import org.openhab.binding.kaleidescape.internal.configuration.KaleidescapeThingConfiguration;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeHandler} is responsible for handling commands, which are sent to one of the channels.
|
||||
*
|
||||
* Based on the Rotel binding by Laurent Garnier
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KaleidescapeHandler extends BaseThingHandler implements KaleidescapeMessageEventListener {
|
||||
private static final long RECON_POLLING_INTERVAL_S = 60;
|
||||
private static final long POLLING_INTERVAL_S = 20;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeHandler.class);
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final Map<String, String> cache = new HashMap<String, String>();
|
||||
|
||||
protected final HttpClient httpClient;
|
||||
protected final Unit<Time> apiSecondUnit = SmartHomeUnits.SECOND;
|
||||
|
||||
private ThingTypeUID thingTypeUID = THING_TYPE_PLAYER;
|
||||
private @Nullable ScheduledFuture<?> reconnectJob;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
private long lastEventReceived = 0;
|
||||
private int updatePeriod = 0;
|
||||
|
||||
protected KaleidescapeConnector connector = new KaleidescapeDefaultConnector();
|
||||
protected int metaRuntimeMultiple = 1;
|
||||
protected int volume = 0;
|
||||
protected boolean volumeEnabled = false;
|
||||
protected boolean isMuted = false;
|
||||
protected String friendlyName = EMPTY;
|
||||
protected Object sequenceLock = new Object();
|
||||
|
||||
public KaleidescapeHandler(Thing thing, SerialPortManager serialPortManager, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
protected void updateChannel(String channelUID, State state) {
|
||||
this.updateState(channelUID, state);
|
||||
}
|
||||
|
||||
protected void updateDetailChannel(String channelUID, State state) {
|
||||
this.updateState(DETAIL + channelUID, state);
|
||||
}
|
||||
|
||||
protected void updateThingProperty(String name, String value) {
|
||||
thing.setProperty(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
final String uid = this.getThing().getUID().getAsString();
|
||||
KaleidescapeThingConfiguration config = getConfigAs(KaleidescapeThingConfiguration.class);
|
||||
|
||||
this.thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
// Check configuration settings
|
||||
String configError = null;
|
||||
final String serialPort = config.serialPort;
|
||||
final String host = config.host;
|
||||
final Integer port = config.port;
|
||||
final Integer updatePeriod = config.updatePeriod;
|
||||
|
||||
if ((serialPort == null || serialPort.isEmpty()) && (host == null || host.isEmpty())) {
|
||||
configError = "undefined serialPort and host configuration settings; please set one of them";
|
||||
} else if (host == null || host.isEmpty()) {
|
||||
if (serialPort != null && serialPort.toLowerCase().startsWith("rfc2217")) {
|
||||
configError = "use host and port configuration settings for a serial over IP connection";
|
||||
}
|
||||
} else {
|
||||
if (port == null) {
|
||||
configError = "undefined port configuration setting";
|
||||
} else if (port <= 0) {
|
||||
configError = "invalid port configuration setting";
|
||||
}
|
||||
}
|
||||
|
||||
if (configError != null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatePeriod != null) {
|
||||
this.updatePeriod = updatePeriod;
|
||||
}
|
||||
|
||||
// check if volume is enabled
|
||||
if (config.volumeEnabled) {
|
||||
this.volumeEnabled = true;
|
||||
this.volume = config.initialVolume;
|
||||
this.updateState(VOLUME, new PercentType(this.volume));
|
||||
this.updateState(MUTE, OnOffType.OFF);
|
||||
}
|
||||
|
||||
if (serialPort != null) {
|
||||
connector = new KaleidescapeSerialConnector(serialPortManager, serialPort, uid);
|
||||
} else if (port != null) {
|
||||
connector = new KaleidescapeIpConnector(host, port, uid);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Either Serial port or Host & Port must be specifed");
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleReconnectJob();
|
||||
schedulePollingJob();
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
cancelReconnectJob();
|
||||
cancelPollingJob();
|
||||
closeConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singletonList(KaleidescapeThingActions.class);
|
||||
}
|
||||
|
||||
public void handleRawCommand(@Nullable String command) {
|
||||
synchronized (sequenceLock) {
|
||||
try {
|
||||
connector.sendCommand(command);
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.warn("K Command: {} failed", command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channel = channelUID.getId();
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
|
||||
return;
|
||||
}
|
||||
synchronized (sequenceLock) {
|
||||
if (!connector.isConnected()) {
|
||||
logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (command instanceof RefreshType) {
|
||||
handleRefresh(channel);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (channel) {
|
||||
case POWER:
|
||||
if (command instanceof OnOffType) {
|
||||
connector.sendCommand(command == OnOffType.ON ? LEAVE_STANDBY : ENTER_STANDBY);
|
||||
}
|
||||
break;
|
||||
case VOLUME:
|
||||
if (command instanceof PercentType) {
|
||||
this.volume = (int) ((PercentType) command).doubleValue();
|
||||
logger.debug("Got volume command {}", this.volume);
|
||||
connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + this.volume);
|
||||
}
|
||||
break;
|
||||
case MUTE:
|
||||
if (command instanceof OnOffType) {
|
||||
this.isMuted = command == OnOffType.ON ? true : false;
|
||||
}
|
||||
connector.sendCommand(SEND_EVENT_MUTE + (this.isMuted ? MUTE_ON : MUTE_OFF));
|
||||
break;
|
||||
case MUSIC_REPEAT:
|
||||
if (command instanceof OnOffType) {
|
||||
connector.sendCommand(command == OnOffType.ON ? MUSIC_REPEAT_ON : MUSIC_REPEAT_OFF);
|
||||
}
|
||||
break;
|
||||
case MUSIC_RANDOM:
|
||||
if (command instanceof OnOffType) {
|
||||
connector.sendCommand(command == OnOffType.ON ? MUSIC_RANDOM_ON : MUSIC_RANDOM_OFF);
|
||||
}
|
||||
break;
|
||||
case CONTROL:
|
||||
case MUSIC_CONTROL:
|
||||
handleControlCommand(command);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Command {} from channel {} failed: unexpected command", command, channel);
|
||||
break;
|
||||
}
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
|
||||
closeConnection();
|
||||
scheduleReconnectJob();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the connection with the Kaleidescape component
|
||||
*
|
||||
* @return true if the connection is opened successfully or false if not
|
||||
*/
|
||||
private synchronized boolean openConnection() {
|
||||
connector.addEventListener(this);
|
||||
try {
|
||||
connector.open();
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("openConnection() failed: {}", e.getMessage());
|
||||
}
|
||||
logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
|
||||
return connector.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection with the Kaleidescape component
|
||||
*/
|
||||
private synchronized void closeConnection() {
|
||||
if (connector.isConnected()) {
|
||||
connector.close();
|
||||
connector.removeEventListener(this);
|
||||
logger.debug("closeConnection(): disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewMessageEvent(KaleidescapeMessageEvent evt) {
|
||||
lastEventReceived = System.currentTimeMillis();
|
||||
|
||||
// check if we are in standby
|
||||
if (STANDBY_MSG.equals(evt.getKey())) {
|
||||
if (!ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.BRIDGE_OFFLINE, STANDBY_MSG);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Use the Enum valueOf to handle the message based on the event key. Otherwise there would be a huge
|
||||
// case statement here
|
||||
KaleidescapeMessageHandler.valueOf(evt.getKey()).handleMessage(evt.getValue(), this);
|
||||
|
||||
if (!evt.isCached()) {
|
||||
cache.put(evt.getKey(), evt.getValue());
|
||||
}
|
||||
|
||||
if (ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
|
||||
// no longer in standby, update the status
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("Unhandled message: key {} = {}", evt.getKey(), evt.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the reconnection job
|
||||
*/
|
||||
private void scheduleReconnectJob() {
|
||||
logger.debug("Schedule reconnect job");
|
||||
cancelReconnectJob();
|
||||
reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
synchronized (sequenceLock) {
|
||||
if (!connector.isConnected()) {
|
||||
logger.debug("Trying to reconnect...");
|
||||
closeConnection();
|
||||
String error = EMPTY;
|
||||
if (openConnection()) {
|
||||
try {
|
||||
cache.clear();
|
||||
Set<String> initialCommands = new HashSet<>(Arrays.asList(GET_DEVICE_TYPE_NAME,
|
||||
GET_FRIENDLY_NAME, GET_DEVICE_INFO, GET_SYSTEM_VERSION, GET_DEVICE_POWER_STATE,
|
||||
GET_CINEMASCAPE_MASK, GET_CINEMASCAPE_MODE, GET_SCALE_MODE, GET_SCREEN_MASK,
|
||||
GET_SCREEN_MASK2, GET_VIDEO_MODE, GET_UI_STATE, GET_HIGHLIGHTED_SELECTION,
|
||||
GET_CHILD_MODE_STATE, GET_PLAY_STATUS, GET_MOVIE_LOCATION, GET_MOVIE_MEDIA_TYPE,
|
||||
GET_PLAYING_TITLE_NAME));
|
||||
|
||||
// Premiere Players and Cinema One support music
|
||||
if (thingTypeUID.equals(THING_TYPE_PLAYER) || thingTypeUID.equals(THING_TYPE_CINEMA_ONE)) {
|
||||
initialCommands.addAll(Arrays.asList(GET_MUSIC_NOW_PLAYING_STATUS,
|
||||
GET_MUSIC_PLAY_STATUS, GET_MUSIC_TITLE));
|
||||
}
|
||||
|
||||
// everything after Premiere Player supports GET_SYSTEM_READINESS_STATE
|
||||
if (!thingTypeUID.equals(THING_TYPE_PLAYER)) {
|
||||
initialCommands.add(GET_SYSTEM_READINESS_STATE);
|
||||
}
|
||||
|
||||
// only Strato supports the GET_*_COLOR commands
|
||||
if (thingTypeUID.equals(THING_TYPE_STRATO)) {
|
||||
initialCommands.addAll(Arrays.asList(GET_VIDEO_COLOR, GET_CONTENT_COLOR));
|
||||
}
|
||||
|
||||
initialCommands.forEach(command -> {
|
||||
try {
|
||||
connector.sendCommand(command);
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("{}: {}", "Error sending initial commands", e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
if (this.updatePeriod == 1) {
|
||||
connector.sendCommand(SET_STATUS_CUE_PERIOD_1);
|
||||
}
|
||||
} catch (KaleidescapeException e) {
|
||||
error = "First command after connection failed";
|
||||
logger.debug("{}: {}", error, e.getMessage());
|
||||
closeConnection();
|
||||
}
|
||||
} else {
|
||||
error = "Reconnection failed";
|
||||
}
|
||||
if (!error.equals(EMPTY)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
|
||||
lastEventReceived = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}, 1, RECON_POLLING_INTERVAL_S, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the reconnection job
|
||||
*/
|
||||
private void cancelReconnectJob() {
|
||||
ScheduledFuture<?> reconnectJob = this.reconnectJob;
|
||||
if (reconnectJob != null) {
|
||||
reconnectJob.cancel(true);
|
||||
this.reconnectJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the polling job
|
||||
*/
|
||||
private void schedulePollingJob() {
|
||||
logger.debug("Schedule polling job");
|
||||
cancelPollingJob();
|
||||
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
synchronized (sequenceLock) {
|
||||
if (connector.isConnected()) {
|
||||
logger.debug("Polling the component for updated status...");
|
||||
try {
|
||||
connector.ping();
|
||||
cache.clear();
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("Polling error: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// if the last successful polling update was more than 1.25 intervals ago,
|
||||
// the component is not responding even though the connection is still good
|
||||
if ((System.currentTimeMillis() - lastEventReceived) > (POLLING_INTERVAL_S * 1.25 * 1000)) {
|
||||
logger.warn("Component not responding to status requests");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Component not responding to status requests");
|
||||
closeConnection();
|
||||
scheduleReconnectJob();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, POLLING_INTERVAL_S, POLLING_INTERVAL_S, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the polling job
|
||||
*/
|
||||
private void cancelPollingJob() {
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
if (pollingJob != null) {
|
||||
pollingJob.cancel(true);
|
||||
this.pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleControlCommand(Command command) throws KaleidescapeException {
|
||||
if (command instanceof PlayPauseType) {
|
||||
if (command == PlayPauseType.PLAY) {
|
||||
connector.sendCommand(PLAY);
|
||||
} else if (command == PlayPauseType.PAUSE) {
|
||||
connector.sendCommand(PAUSE);
|
||||
}
|
||||
} else if (command instanceof NextPreviousType) {
|
||||
if (command == NextPreviousType.NEXT) {
|
||||
connector.sendCommand(NEXT);
|
||||
} else if (command == NextPreviousType.PREVIOUS) {
|
||||
connector.sendCommand(PREVIOUS);
|
||||
}
|
||||
} else if (command instanceof RewindFastforwardType) {
|
||||
if (command == RewindFastforwardType.FASTFORWARD) {
|
||||
connector.sendCommand(SCAN_FORWARD);
|
||||
} else if (command == RewindFastforwardType.REWIND) {
|
||||
connector.sendCommand(SCAN_REVERSE);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unknown control command: {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRefresh(String channel) throws KaleidescapeException {
|
||||
switch (channel) {
|
||||
case POWER:
|
||||
connector.sendCommand(GET_DEVICE_POWER_STATE, cache.get("DEVICE_POWER_STATE"));
|
||||
break;
|
||||
case VOLUME:
|
||||
updateState(channel, new PercentType(this.volume));
|
||||
break;
|
||||
case MUTE:
|
||||
updateState(channel, this.isMuted ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
case TITLE_NAME:
|
||||
connector.sendCommand(GET_PLAYING_TITLE_NAME, cache.get("TITLE_NAME"));
|
||||
break;
|
||||
case PLAY_MODE:
|
||||
case PLAY_SPEED:
|
||||
case TITLE_NUM:
|
||||
case TITLE_LENGTH:
|
||||
case TITLE_LOC:
|
||||
case CHAPTER_NUM:
|
||||
case CHAPTER_LENGTH:
|
||||
case CHAPTER_LOC:
|
||||
connector.sendCommand(GET_PLAY_STATUS, cache.get("PLAY_STATUS"));
|
||||
break;
|
||||
case MOVIE_MEDIA_TYPE:
|
||||
connector.sendCommand(GET_MOVIE_MEDIA_TYPE, cache.get("MOVIE_MEDIA_TYPE"));
|
||||
break;
|
||||
case MOVIE_LOCATION:
|
||||
connector.sendCommand(GET_MOVIE_LOCATION, cache.get("MOVIE_LOCATION"));
|
||||
break;
|
||||
case VIDEO_MODE:
|
||||
case VIDEO_MODE_COMPOSITE:
|
||||
case VIDEO_MODE_COMPONENT:
|
||||
case VIDEO_MODE_HDMI:
|
||||
connector.sendCommand(GET_VIDEO_MODE, cache.get("VIDEO_MODE"));
|
||||
break;
|
||||
case VIDEO_COLOR:
|
||||
case VIDEO_COLOR_EOTF:
|
||||
connector.sendCommand(GET_VIDEO_COLOR, cache.get("VIDEO_COLOR"));
|
||||
break;
|
||||
case CONTENT_COLOR:
|
||||
case CONTENT_COLOR_EOTF:
|
||||
connector.sendCommand(GET_CONTENT_COLOR, cache.get("CONTENT_COLOR"));
|
||||
break;
|
||||
case SCALE_MODE:
|
||||
connector.sendCommand(GET_SCALE_MODE, cache.get("SCALE_MODE"));
|
||||
break;
|
||||
case ASPECT_RATIO:
|
||||
case SCREEN_MASK:
|
||||
connector.sendCommand(GET_SCREEN_MASK, cache.get("SCREEN_MASK"));
|
||||
break;
|
||||
case SCREEN_MASK2:
|
||||
connector.sendCommand(GET_SCREEN_MASK2, cache.get("SCREEN_MASK2"));
|
||||
break;
|
||||
case CINEMASCAPE_MASK:
|
||||
connector.sendCommand(GET_CINEMASCAPE_MASK, cache.get("GET_CINEMASCAPE_MASK"));
|
||||
break;
|
||||
case CINEMASCAPE_MODE:
|
||||
connector.sendCommand(GET_CINEMASCAPE_MODE, cache.get("CINEMASCAPE_MODE"));
|
||||
break;
|
||||
case UI_STATE:
|
||||
connector.sendCommand(GET_UI_STATE, cache.get("UI_STATE"));
|
||||
break;
|
||||
case CHILD_MODE_STATE:
|
||||
connector.sendCommand(GET_CHILD_MODE_STATE, cache.get("CHILD_MODE_STATE"));
|
||||
break;
|
||||
case SYSTEM_READINESS_STATE:
|
||||
connector.sendCommand(GET_SYSTEM_READINESS_STATE, cache.get("SYSTEM_READINESS_STATE"));
|
||||
break;
|
||||
case HIGHLIGHTED_SELECTION:
|
||||
connector.sendCommand(GET_HIGHLIGHTED_SELECTION, cache.get("HIGHLIGHTED_SELECTION"));
|
||||
break;
|
||||
case USER_DEFINED_EVENT:
|
||||
case USER_INPUT:
|
||||
case USER_INPUT_PROMPT:
|
||||
updateState(channel, StringType.EMPTY);
|
||||
break;
|
||||
case MUSIC_REPEAT:
|
||||
case MUSIC_RANDOM:
|
||||
connector.sendCommand(GET_MUSIC_NOW_PLAYING_STATUS, cache.get("MUSIC_NOW_PLAYING_STATUS"));
|
||||
break;
|
||||
case MUSIC_TRACK:
|
||||
case MUSIC_ARTIST:
|
||||
case MUSIC_ALBUM:
|
||||
case MUSIC_TRACK_HANDLE:
|
||||
case MUSIC_ALBUM_HANDLE:
|
||||
case MUSIC_NOWPLAY_HANDLE:
|
||||
connector.sendCommand(GET_MUSIC_TITLE, cache.get("MUSIC_TITLE"));
|
||||
break;
|
||||
case MUSIC_PLAY_MODE:
|
||||
case MUSIC_PLAY_SPEED:
|
||||
case MUSIC_TRACK_LENGTH:
|
||||
case MUSIC_TRACK_POSITION:
|
||||
case MUSIC_TRACK_PROGRESS:
|
||||
connector.sendCommand(GET_MUSIC_PLAY_STATUS, cache.get("MUSIC_PLAY_STATUS"));
|
||||
break;
|
||||
case DETAIL_TYPE:
|
||||
case DETAIL_TITLE:
|
||||
case DETAIL_ALBUM_TITLE:
|
||||
case DETAIL_COVER_ART:
|
||||
case DETAIL_COVER_URL:
|
||||
case DETAIL_HIRES_COVER_URL:
|
||||
case DETAIL_RATING:
|
||||
case DETAIL_YEAR:
|
||||
case DETAIL_RUNNING_TIME:
|
||||
case DETAIL_ACTORS:
|
||||
case DETAIL_ARTIST:
|
||||
case DETAIL_DIRECTORS:
|
||||
case DETAIL_GENRES:
|
||||
case DETAIL_RATING_REASON:
|
||||
case DETAIL_SYNOPSIS:
|
||||
case DETAIL_REVIEW:
|
||||
case DETAIL_COLOR_DESCRIPTION:
|
||||
case DETAIL_COUNTRY:
|
||||
case DETAIL_ASPECT_RATIO:
|
||||
case DETAIL_DISC_LOCATION:
|
||||
updateState(channel, StringType.EMPTY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
/**
|
||||
* 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.kaleidescape.internal.handler;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||
import static org.eclipse.jetty.http.HttpStatus.OK_200;
|
||||
import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants;
|
||||
import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeFormatter;
|
||||
import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeStatusCodes;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KaleidescapeMessageHandler} class processes all messages received
|
||||
* by the event listener and then updates the appropriate states
|
||||
*
|
||||
* @author Michael Lobstein - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum KaleidescapeMessageHandler {
|
||||
UI_STATE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.UI_STATE, new StringType(message));
|
||||
}
|
||||
},
|
||||
HIGHLIGHTED_SELECTION {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.HIGHLIGHTED_SELECTION, new StringType(message));
|
||||
}
|
||||
},
|
||||
DEVICE_POWER_STATE {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 1:1
|
||||
// power_state, zone 1 state, zone n state
|
||||
private final Pattern p = Pattern.compile("^(\\d{1}):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(POWER, (ONE).equals(matcher.group(1)) ? OnOffType.ON : OnOffType.OFF);
|
||||
} else {
|
||||
logger.debug("DEVICE_POWER_STATE - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
TITLE_NAME {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.TITLE_NAME,
|
||||
new StringType(KaleidescapeFormatter.formatString(message)));
|
||||
}
|
||||
},
|
||||
PLAY_STATUS {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 0:0:00:00000:00000:000:00000:00000
|
||||
// mode, speed, title_num, title_length, title_loc, chapter_num, chapter_length, chapter_loc
|
||||
private final Pattern p = Pattern
|
||||
.compile("^(\\d{1}):(\\d{1}):(\\d{2}):(\\d{5}):(\\d{5}):(\\d{3}):(\\d{5}):(\\d{5})$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(PLAY_MODE,
|
||||
new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
|
||||
|
||||
handler.updateChannel(PLAY_SPEED, new StringType(matcher.group(2)));
|
||||
|
||||
handler.updateChannel(TITLE_NUM, new DecimalType(Integer.parseInt(matcher.group(3))));
|
||||
|
||||
handler.updateChannel(TITLE_LENGTH,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
|
||||
|
||||
handler.updateChannel(TITLE_LOC,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(5)), handler.apiSecondUnit));
|
||||
|
||||
handler.updateChannel(CHAPTER_NUM, new DecimalType(Integer.parseInt(matcher.group(6))));
|
||||
|
||||
handler.updateChannel(CHAPTER_LENGTH,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(7)), handler.apiSecondUnit));
|
||||
|
||||
handler.updateChannel(CHAPTER_LOC,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(8)), handler.apiSecondUnit));
|
||||
} else {
|
||||
logger.debug("PLAY_STATUS - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
MOVIE_MEDIA_TYPE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.MOVIE_MEDIA_TYPE,
|
||||
new StringType(KaleidescapeStatusCodes.MEDIA_TYPE.get(message)));
|
||||
}
|
||||
},
|
||||
MOVIE_LOCATION {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.MOVIE_LOCATION,
|
||||
new StringType(KaleidescapeStatusCodes.MOVIE_LOCATION.get(message)));
|
||||
}
|
||||
},
|
||||
VIDEO_MODE {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 00:00:00
|
||||
// composite, component, hdmi
|
||||
private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2})$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.VIDEO_MODE, new StringType(message));
|
||||
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(VIDEO_MODE_COMPOSITE,
|
||||
new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(1))));
|
||||
|
||||
handler.updateChannel(VIDEO_MODE_COMPONENT,
|
||||
new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(2))));
|
||||
|
||||
handler.updateChannel(VIDEO_MODE_HDMI,
|
||||
new StringType(KaleidescapeStatusCodes.VIDEO_MODE.get(matcher.group(3))));
|
||||
} else {
|
||||
logger.debug("VIDEO_MODE - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
VIDEO_COLOR {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 02:01:24:01
|
||||
// eotf, color_space, color_depth, color_sampling
|
||||
private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.VIDEO_COLOR, new StringType(message));
|
||||
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(VIDEO_COLOR_EOTF,
|
||||
new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
|
||||
} else {
|
||||
logger.debug("VIDEO_COLOR - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
CONTENT_COLOR {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 02:01:24:01
|
||||
// eotf, color_space, color_depth, color_sampling
|
||||
private final Pattern p = Pattern.compile("^(\\d{2}):(\\d{2}):(\\d{2}):(\\d{2})$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.CONTENT_COLOR, new StringType(message));
|
||||
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(CONTENT_COLOR_EOTF,
|
||||
new StringType(KaleidescapeStatusCodes.EOTF.get(matcher.group(1))));
|
||||
} else {
|
||||
logger.debug("CONTENT_COLOR - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
SCALE_MODE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.SCALE_MODE, new StringType(message));
|
||||
}
|
||||
},
|
||||
SCREEN_MASK {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK, new StringType(message));
|
||||
|
||||
// per API reference rev 3.3.1, ASPECT_RATIO message should not be used
|
||||
// the first element of SCREEN_MASK now provides this info
|
||||
if (!message.equals(EMPTY)) {
|
||||
String[] msgSplit = message.split(":", 2);
|
||||
handler.updateChannel(KaleidescapeBindingConstants.ASPECT_RATIO,
|
||||
new StringType(KaleidescapeStatusCodes.ASPECT_RATIO.get(msgSplit[0])));
|
||||
}
|
||||
}
|
||||
},
|
||||
SCREEN_MASK2 {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.SCREEN_MASK2, new StringType(message));
|
||||
}
|
||||
},
|
||||
CINEMASCAPE_MASK {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MASK, new StringType(message));
|
||||
}
|
||||
},
|
||||
CINEMASCAPE_MODE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.CINEMASCAPE_MODE, new StringType(message));
|
||||
}
|
||||
},
|
||||
CHILD_MODE_STATE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
handler.updateChannel(KaleidescapeBindingConstants.CHILD_MODE_STATE, new StringType(message));
|
||||
}
|
||||
},
|
||||
MUSIC_TITLE {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: You:Radiohead:Pablo Honey:1.9b5f4d786d7e2c49-t301_577:1.R_1493833:2.200c5
|
||||
// track, artist, album, track handle, album handle, now playing handle
|
||||
private final Pattern p = Pattern.compile("^(.*):(.*):(.*):(.*):(.*):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// first replace delimited : in track/artist/album name with ||, fix it later in formatString()
|
||||
Matcher matcher = p.matcher(message.replace("\\:", "||"));
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(MUSIC_TRACK,
|
||||
new StringType(KaleidescapeFormatter.formatString(matcher.group(1))));
|
||||
|
||||
handler.updateChannel(MUSIC_ARTIST,
|
||||
new StringType(KaleidescapeFormatter.formatString(matcher.group(2))));
|
||||
|
||||
handler.updateChannel(MUSIC_ALBUM,
|
||||
new StringType(KaleidescapeFormatter.formatString(matcher.group(3))));
|
||||
|
||||
handler.updateChannel(MUSIC_TRACK_HANDLE, new StringType(matcher.group(4)));
|
||||
|
||||
handler.updateChannel(MUSIC_ALBUM_HANDLE, new StringType(matcher.group(5)));
|
||||
|
||||
handler.updateChannel(MUSIC_NOWPLAY_HANDLE, new StringType(matcher.group(6)));
|
||||
} else {
|
||||
logger.debug("MUSIC_TITLE - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
MUSIC_PLAY_STATUS {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 2:0:00207:+00000:000.00
|
||||
// 2:0:00331:+00183:055.29
|
||||
// mode, speed, track length, track position, track progress %
|
||||
private final Pattern p = Pattern.compile("^(\\d{1}):(\\d{1}):(\\d{5}):(.\\d{5}):(\\d{3}\\.\\d{2})$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateChannel(MUSIC_PLAY_MODE,
|
||||
new StringType(KaleidescapeStatusCodes.PLAY_MODE.get(matcher.group(1))));
|
||||
|
||||
handler.updateChannel(MUSIC_PLAY_SPEED, new StringType(matcher.group(2)));
|
||||
|
||||
handler.updateChannel(MUSIC_TRACK_LENGTH,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(3)), handler.apiSecondUnit));
|
||||
|
||||
handler.updateChannel(MUSIC_TRACK_POSITION,
|
||||
new QuantityType<Time>(Integer.parseInt(matcher.group(4)), handler.apiSecondUnit));
|
||||
|
||||
handler.updateChannel(MUSIC_TRACK_PROGRESS,
|
||||
new DecimalType(BigDecimal.valueOf(Math.round(Double.parseDouble(matcher.group(5))))));
|
||||
} else {
|
||||
logger.debug("MUSIC_PLAY_STATUS - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
MUSIC_NOW_PLAYING_STATUS {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 00013:00000:0:0:0000000238:2.200c5
|
||||
// total # tracks in list, list index, repeat, random, generation, now_playing handle
|
||||
// only using repeat & random right now
|
||||
private final Pattern p = Pattern.compile("^(\\d{5}):(\\d{5}):(\\d{1}):(\\d{1}):(\\d{10}):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
// update REPEAT switch state
|
||||
handler.updateChannel(MUSIC_REPEAT, (ONE).equals(matcher.group(3)) ? OnOffType.ON : OnOffType.OFF);
|
||||
|
||||
// update RANDOM switch state
|
||||
handler.updateChannel(MUSIC_RANDOM, (ONE).equals(matcher.group(4)) ? OnOffType.ON : OnOffType.OFF);
|
||||
} else {
|
||||
logger.debug("MUSIC_NOW_PLAYING_STATUS - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
PLAYING_MUSIC_INFORMATION {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: R_1493833:Radiohead - Pablo Honey
|
||||
// album handle, artist - album
|
||||
// do nothing; redundant
|
||||
}
|
||||
},
|
||||
CONTENT_DETAILS {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// g1=meta id, g2=meta type, g3=data
|
||||
// example: 6:Year:1995
|
||||
// or: 10:Genres:Pop\/Rock
|
||||
private final Pattern p = Pattern.compile("^(\\d{1,2}):([^:^/]*):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
String metaType = matcher.group(2).toLowerCase();
|
||||
String value = KaleidescapeFormatter.formatString(matcher.group(3));
|
||||
|
||||
// the CONTENT_DETAILS message with id=1 tells us what type of meta data is coming
|
||||
if (ONE.equals(matcher.group(1))) {
|
||||
if ((CONTENT_HANDLE).equals(metaType)) {
|
||||
handler.updateDetailChannel(DETAIL_TYPE, new StringType(MOVIE));
|
||||
handler.metaRuntimeMultiple = 60;
|
||||
|
||||
// null out album specific
|
||||
handler.updateDetailChannel(DETAIL_ALBUM_TITLE, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_ARTIST, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_REVIEW, UnDefType.NULL);
|
||||
|
||||
} else if ((ALBUM_CONTENT_HANDLE).equals(metaType)) {
|
||||
handler.updateDetailChannel(DETAIL_TYPE, new StringType(ALBUM));
|
||||
handler.metaRuntimeMultiple = 1;
|
||||
|
||||
// null out movie specific
|
||||
handler.updateDetailChannel(DETAIL_TITLE, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_RATING, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_ACTORS, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_DIRECTORS, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_RATING_REASON, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_SYNOPSIS, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_COLOR_DESCRIPTION, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_COUNTRY, UnDefType.NULL);
|
||||
handler.updateDetailChannel(DETAIL_ASPECT_RATIO, UnDefType.NULL);
|
||||
|
||||
} else {
|
||||
handler.updateDetailChannel(DETAIL_TYPE, UnDefType.UNDEF);
|
||||
}
|
||||
// otherwise update the channel if it is one we care about
|
||||
} else if (METADATA_CHANNELS.contains(metaType)) {
|
||||
// special case for cover art image
|
||||
if (DETAIL_COVER_URL.equals(metaType)) {
|
||||
handler.updateDetailChannel(metaType, new StringType(value));
|
||||
if (!value.isEmpty()) {
|
||||
try {
|
||||
ContentResponse contentResponse = handler.httpClient.newRequest(value).method(GET)
|
||||
.timeout(10, TimeUnit.SECONDS).send();
|
||||
int httpStatus = contentResponse.getStatus();
|
||||
if (httpStatus == OK_200) {
|
||||
handler.updateDetailChannel(DETAIL_COVER_ART,
|
||||
new RawType(contentResponse.getContent(), RawType.DEFAULT_MIME_TYPE));
|
||||
} else {
|
||||
handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Error updating Cover Art Image channel for url: {}", value);
|
||||
handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
|
||||
}
|
||||
} else {
|
||||
handler.updateDetailChannel(DETAIL_COVER_ART, UnDefType.NULL);
|
||||
}
|
||||
// special case for running time to create a QuantityType<Time>
|
||||
} else if (DETAIL_RUNNING_TIME.equals(metaType)) {
|
||||
handler.updateDetailChannel(DETAIL_RUNNING_TIME, new QuantityType<Time>(
|
||||
Integer.parseInt(value) * handler.metaRuntimeMultiple, handler.apiSecondUnit));
|
||||
// everything else just send it as a string
|
||||
} else {
|
||||
handler.updateDetailChannel(metaType, new StringType(value));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("CONTENT_DETAILS - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
TIME {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// do nothing
|
||||
}
|
||||
},
|
||||
STATUS_CUE_PERIOD {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// do nothing
|
||||
}
|
||||
},
|
||||
ASPECT_RATIO {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// per API reference rev 3.3.1, ASPECT_RATIO message should not be used
|
||||
// the first element of SCREEN_MASK now provides this info
|
||||
}
|
||||
},
|
||||
USER_DEFINED_EVENT {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: SELECT_KALEIDESCAPE_INPUT
|
||||
try {
|
||||
switch (message) {
|
||||
// when the ipad or phone app is started up, it sends a VOLUME_QUERY,
|
||||
// so we respond to enable volume controls and set the initial volume and mute
|
||||
case "VOLUME_QUERY":
|
||||
if (handler.volumeEnabled) {
|
||||
synchronized (handler.sequenceLock) {
|
||||
handler.connector.sendCommand(SEND_EVENT_VOLUME_CAPABILITIES_15);
|
||||
handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
|
||||
handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "VOLUME_UP":
|
||||
if (handler.volumeEnabled) {
|
||||
synchronized (handler.sequenceLock) {
|
||||
handler.volume++;
|
||||
handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
|
||||
handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "VOLUME_DOWN":
|
||||
if (handler.volumeEnabled) {
|
||||
synchronized (handler.sequenceLock) {
|
||||
handler.volume--;
|
||||
handler.updateChannel(VOLUME, new PercentType(BigDecimal.valueOf(handler.volume)));
|
||||
handler.connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + handler.volume);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "TOGGLE_MUTE":
|
||||
if (handler.volumeEnabled) {
|
||||
State state = UnDefType.UNDEF;
|
||||
synchronized (handler.sequenceLock) {
|
||||
if (handler.isMuted) {
|
||||
state = OnOffType.OFF;
|
||||
handler.isMuted = false;
|
||||
} else {
|
||||
state = OnOffType.ON;
|
||||
handler.isMuted = true;
|
||||
}
|
||||
handler.connector.sendCommand(SEND_EVENT_MUTE + (handler.isMuted ? MUTE_ON : MUTE_OFF));
|
||||
handler.updateChannel(MUTE, state);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// the default is to just publish all other USER_DEFINED_EVENTs
|
||||
default:
|
||||
handler.updateChannel(KaleidescapeBindingConstants.USER_DEFINED_EVENT, new StringType(message));
|
||||
}
|
||||
} catch (KaleidescapeException e) {
|
||||
logger.debug("USER_DEFINED_EVENT - exception on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
USER_INPUT {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: 01:Search for title:ABC
|
||||
handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
|
||||
}
|
||||
},
|
||||
USER_INPUT_PROMPT {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: 00:00::00:0:1
|
||||
handler.updateChannel(KaleidescapeBindingConstants.USER_INPUT, new StringType(message));
|
||||
}
|
||||
},
|
||||
SYSTEM_READINESS_STATE {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example 1, 2 or 3
|
||||
handler.updateChannel(KaleidescapeBindingConstants.SYSTEM_READINESS_STATE,
|
||||
new StringType(KaleidescapeStatusCodes.READINESS_STATE.get(message)));
|
||||
}
|
||||
},
|
||||
SYSTEM_VERSION {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 16:8.6.0-21023
|
||||
// protocol version, kOS version
|
||||
private final Pattern p = Pattern.compile("^(\\d{2}):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
handler.updateThingProperty(PROPERTY_PROTOCOL_VERSION, matcher.group(1));
|
||||
handler.updateThingProperty(PROPERTY_SYSTEM_VERSION, matcher.group(2));
|
||||
} else {
|
||||
logger.debug("SYSTEM_VERSION - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
DEVICE_INFO {
|
||||
private final Logger logger = LoggerFactory.getLogger(KaleidescapeMessageHandler.class);
|
||||
|
||||
// example: 07:000000000000558F:00:192.168.001.100
|
||||
// device type (deprecated), serial number, cpdid, ip address
|
||||
private final Pattern p = Pattern.compile("^(\\d{2}):(.*):(\\d{2}):(.*)$");
|
||||
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
Matcher matcher = p.matcher(message);
|
||||
if (matcher.find()) {
|
||||
// replaceFirst takes off leading zeros
|
||||
handler.updateThingProperty(PROPERTY_SERIAL_NUMBER, matcher.group(2).replaceFirst("^0+(?!$)", EMPTY));
|
||||
handler.updateThingProperty(PROPERTY_CONTROL_PROTOCOL_ID, matcher.group(3));
|
||||
} else {
|
||||
logger.debug("DEVICE_INFO - no match on message: {}", message);
|
||||
}
|
||||
}
|
||||
},
|
||||
DEVICE_TYPE_NAME {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: 'Player' or 'Strato'
|
||||
handler.updateThingProperty(PROPERTY_COMPONENT_TYPE, message);
|
||||
}
|
||||
},
|
||||
FRIENDLY_NAME {
|
||||
@Override
|
||||
public void handleMessage(String message, KaleidescapeHandler handler) {
|
||||
// example: 'Living Room'
|
||||
handler.friendlyName = message;
|
||||
handler.updateThingProperty(PROPERTY_FRIENDLY_NAME, message);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract void handleMessage(String message, KaleidescapeHandler handler);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="kaleidescape" 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>Kaleidescape System Binding</name>
|
||||
<description>Controls a Kaleidescape System Movie Player</description>
|
||||
<author>Michael Lobstein</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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:kaleidescape:kaleidescapedevice">
|
||||
<parameter name="host" type="text" required="false">
|
||||
<context>network-address</context>
|
||||
<label>Address</label>
|
||||
<description>Host Name or IP Address of the Kaleidescape component.</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" min="1" max="65535" required="false">
|
||||
<label>Port</label>
|
||||
<description>Communication Port for IP Connection to the Kaleidescape component.</description>
|
||||
<default>10000</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="serialPort" type="text" required="false">
|
||||
<context>serial-port</context>
|
||||
<label>Serial Port</label>
|
||||
<description>(Optional) Serial Port to use for connecting directly to an individual Kaleidescape component.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="updatePeriod" type="integer" min="0" max="1" unit="s" required="false">
|
||||
<label>Update Period</label>
|
||||
<description>Tells the component how often time status updates should be sent; Values greater than 1 are not yet
|
||||
implmented by the protocol. Setting to 1 may impact openHAB system performance due
|
||||
to constant updates while content
|
||||
playing.</description>
|
||||
<default>0</default>
|
||||
</parameter>
|
||||
<parameter name="volumeEnabled" type="boolean" required="false">
|
||||
<label>Volume Control Enabled</label>
|
||||
<description>Enable the Volume and Mute controls in the Kaleidescape iPad & Phone apps and track their status in
|
||||
the binding. Disabled by default to prevent conflicts with other control systems that may already be controlling
|
||||
volume on this zone.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="initialVolume" type="integer" min="0" max="75" unit="%" required="false">
|
||||
<label>Initial Volume Setting</label>
|
||||
<description>When the binding starts up, set the Inital Volume level to this value (Default 25).</description>
|
||||
<default>25</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,796 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="kaleidescape"
|
||||
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">
|
||||
|
||||
<!-- Kaleidescape Movie Player (Any KPlayer, M Class [M300, M500, M700] or Cinema One 1st Gen) Thing -->
|
||||
<thing-type id="player">
|
||||
<label>Kaleidescape Player</label>
|
||||
<description>
|
||||
A Kaleidescape Movie Player (KPlayer, M Class [M300, M500, M700] or Cinema One 1st Gen)
|
||||
</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="ui" typeId="ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and Information for the Player's On Screen Interface</description>
|
||||
</channel-group>
|
||||
<channel-group id="music" typeId="music">
|
||||
<label>Music Zone</label>
|
||||
<description>Controls and Information for the Player's Music Zone</description>
|
||||
</channel-group>
|
||||
<channel-group id="detail" typeId="detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains Metadata About a Selected Item</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<properties>
|
||||
<property name="Component Type">unknown</property>
|
||||
<property name="Friendly Name">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Control Protocol ID">unknown</property>
|
||||
<property name="System Version">unknown</property>
|
||||
<property name="Protocol Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<config-description-ref uri="thing-type:kaleidescape:kaleidescapedevice"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Kaleidescape Cinema One (2nd Generation) Thing -->
|
||||
<thing-type id="cinemaone">
|
||||
<label>Kaleidescape Cinema One (2nd Generation)</label>
|
||||
<description>
|
||||
A Kaleidescape Cinema One (2nd Generation) Player
|
||||
</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="ui" typeId="c1-alto_ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and Information for the Cinema One's On Screen Interface</description>
|
||||
</channel-group>
|
||||
<channel-group id="music" typeId="music">
|
||||
<label>Music Zone</label>
|
||||
<description>Controls and Information for the Cinema One's Music Zone</description>
|
||||
</channel-group>
|
||||
<channel-group id="detail" typeId="detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains Metadata About a Selected Item</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<properties>
|
||||
<property name="Component Type">unknown</property>
|
||||
<property name="Friendly Name">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Control Protocol ID">unknown</property>
|
||||
<property name="System Version">unknown</property>
|
||||
<property name="Protocol Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<config-description-ref uri="thing-type:kaleidescape:kaleidescapedevice"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Kaleidescape Alto Thing -->
|
||||
<thing-type id="alto">
|
||||
<label>Kaleidescape Alto</label>
|
||||
<description>
|
||||
A Kaleidescape Alto Player
|
||||
</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="ui" typeId="c1-alto_ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and Information for the Alto's On Screen Interface</description>
|
||||
</channel-group>
|
||||
<channel-group id="detail" typeId="alto-strato_detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains Metadata About a Selected Item</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<properties>
|
||||
<property name="Component Type">unknown</property>
|
||||
<property name="Friendly Name">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Control Protocol ID">unknown</property>
|
||||
<property name="System Version">unknown</property>
|
||||
<property name="Protocol Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<config-description-ref uri="thing-type:kaleidescape:kaleidescapedevice"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Kaleidescape Strato Thing -->
|
||||
<thing-type id="strato">
|
||||
<label>Kaleidescape Strato</label>
|
||||
<description>
|
||||
A Kaleidescape Strato Player
|
||||
</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="ui" typeId="strato_ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and Information for the Strato's On Screen Interface</description>
|
||||
</channel-group>
|
||||
<channel-group id="detail" typeId="alto-strato_detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains Metadata About a Selected Item</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<properties>
|
||||
<property name="Component Type">unknown</property>
|
||||
<property name="Friendly Name">unknown</property>
|
||||
<property name="Serial Number">unknown</property>
|
||||
<property name="Control Protocol ID">unknown</property>
|
||||
<property name="System Version">unknown</property>
|
||||
<property name="Protocol Version">unknown</property>
|
||||
</properties>
|
||||
|
||||
<config-description-ref uri="thing-type:kaleidescape:kaleidescapedevice"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and information for the player's on screen interface</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="mute" typeId="system.mute"/>
|
||||
<channel id="control" typeId="movie_control"/>
|
||||
<channel id="title_name" typeId="title_name"/>
|
||||
<channel id="play_mode" typeId="movie_play_mode"/>
|
||||
<channel id="play_speed" typeId="movie_play_speed"/>
|
||||
<channel id="title_num" typeId="title_num"/>
|
||||
<channel id="title_length" typeId="title_length"/>
|
||||
<channel id="title_loc" typeId="title_loc"/>
|
||||
<channel id="chapter_num" typeId="chapter_num"/>
|
||||
<channel id="chapter_length" typeId="chapter_length"/>
|
||||
<channel id="chapter_loc" typeId="chapter_loc"/>
|
||||
<channel id="movie_media_type" typeId="movie_media_type"/>
|
||||
<channel id="movie_location" typeId="movie_location"/>
|
||||
<channel id="aspect_ratio" typeId="aspect_ratio"/>
|
||||
<channel id="video_mode" typeId="video_mode"/>
|
||||
<channel id="video_mode_composite" typeId="video_mode_composite"/>
|
||||
<channel id="video_mode_component" typeId="video_mode_component"/>
|
||||
<channel id="video_mode_hdmi" typeId="video_mode_hdmi"/>
|
||||
<channel id="scale_mode" typeId="scale_mode"/>
|
||||
<channel id="screen_mask" typeId="screen_mask"/>
|
||||
<channel id="screen_mask2" typeId="screen_mask2"/>
|
||||
<channel id="cinemascape_mask" typeId="cinemascape_mask"/>
|
||||
<channel id="cinemascape_mode" typeId="cinemascape_mode"/>
|
||||
<channel id="ui_state" typeId="ui_state"/>
|
||||
<channel id="child_mode_state" typeId="child_mode_state"/>
|
||||
<channel id="highlighted_selection" typeId="highlighted_selection"/>
|
||||
<channel id="user_defined_event" typeId="user_defined_event"/>
|
||||
<channel id="user_input" typeId="user_input"/>
|
||||
<channel id="user_input_prompt" typeId="user_input_prompt"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- C1/Alto UI channels (remove analog video channels and add readiness_state) -->
|
||||
<channel-group-type id="c1-alto_ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and information for the player's on screen interface</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="mute" typeId="system.mute"/>
|
||||
<channel id="control" typeId="movie_control"/>
|
||||
<channel id="title_name" typeId="title_name"/>
|
||||
<channel id="play_mode" typeId="movie_play_mode"/>
|
||||
<channel id="play_speed" typeId="movie_play_speed"/>
|
||||
<channel id="title_num" typeId="title_num"/>
|
||||
<channel id="title_length" typeId="title_length"/>
|
||||
<channel id="title_loc" typeId="title_loc"/>
|
||||
<channel id="chapter_num" typeId="chapter_num"/>
|
||||
<channel id="chapter_length" typeId="chapter_length"/>
|
||||
<channel id="chapter_loc" typeId="chapter_loc"/>
|
||||
<channel id="movie_media_type" typeId="movie_media_type"/>
|
||||
<channel id="movie_location" typeId="movie_location"/>
|
||||
<channel id="aspect_ratio" typeId="aspect_ratio"/>
|
||||
<channel id="video_mode" typeId="video_mode"/>
|
||||
<channel id="video_mode_hdmi" typeId="video_mode_hdmi"/>
|
||||
<channel id="scale_mode" typeId="scale_mode"/>
|
||||
<channel id="screen_mask" typeId="screen_mask"/>
|
||||
<channel id="screen_mask2" typeId="screen_mask2"/>
|
||||
<channel id="cinemascape_mask" typeId="cinemascape_mask"/>
|
||||
<channel id="cinemascape_mode" typeId="cinemascape_mode"/>
|
||||
<channel id="ui_state" typeId="ui_state"/>
|
||||
<channel id="child_mode_state" typeId="child_mode_state"/>
|
||||
<channel id="readiness_state" typeId="readiness_state"/>
|
||||
<channel id="highlighted_selection" typeId="highlighted_selection"/>
|
||||
<channel id="user_defined_event" typeId="user_defined_event"/>
|
||||
<channel id="user_input" typeId="user_input"/>
|
||||
<channel id="user_input_prompt" typeId="user_input_prompt"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- Strato UI channels (add *color channels for 4K/HDR info) -->
|
||||
<channel-group-type id="strato_ui">
|
||||
<label>User Interface</label>
|
||||
<description>Controls and information for the player's on screen interface</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="mute" typeId="system.mute"/>
|
||||
<channel id="control" typeId="movie_control"/>
|
||||
<channel id="title_name" typeId="title_name"/>
|
||||
<channel id="play_mode" typeId="movie_play_mode"/>
|
||||
<channel id="play_speed" typeId="movie_play_speed"/>
|
||||
<channel id="title_num" typeId="title_num"/>
|
||||
<channel id="title_length" typeId="title_length"/>
|
||||
<channel id="title_loc" typeId="title_loc"/>
|
||||
<channel id="chapter_num" typeId="chapter_num"/>
|
||||
<channel id="chapter_length" typeId="chapter_length"/>
|
||||
<channel id="chapter_loc" typeId="chapter_loc"/>
|
||||
<channel id="movie_media_type" typeId="movie_media_type"/>
|
||||
<channel id="movie_location" typeId="movie_location"/>
|
||||
<channel id="aspect_ratio" typeId="aspect_ratio"/>
|
||||
<channel id="video_mode" typeId="video_mode"/>
|
||||
<channel id="video_mode_hdmi" typeId="video_mode_hdmi"/>
|
||||
<channel id="video_color" typeId="video_color"/>
|
||||
<channel id="video_color_eotf" typeId="video_color_eotf"/>
|
||||
<channel id="content_color" typeId="content_color"/>
|
||||
<channel id="content_color_eotf" typeId="content_color_eotf"/>
|
||||
<channel id="scale_mode" typeId="scale_mode"/>
|
||||
<channel id="screen_mask" typeId="screen_mask"/>
|
||||
<channel id="screen_mask2" typeId="screen_mask2"/>
|
||||
<channel id="cinemascape_mask" typeId="cinemascape_mask"/>
|
||||
<channel id="cinemascape_mode" typeId="cinemascape_mode"/>
|
||||
<channel id="ui_state" typeId="ui_state"/>
|
||||
<channel id="child_mode_state" typeId="child_mode_state"/>
|
||||
<channel id="readiness_state" typeId="readiness_state"/>
|
||||
<channel id="highlighted_selection" typeId="highlighted_selection"/>
|
||||
<channel id="user_defined_event" typeId="user_defined_event"/>
|
||||
<channel id="user_input" typeId="user_input"/>
|
||||
<channel id="user_input_prompt" typeId="user_input_prompt"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="music">
|
||||
<label>Music Zone</label>
|
||||
<description>Controls and information for the player's music zone</description>
|
||||
<channels>
|
||||
<channel id="control" typeId="music_control"/>
|
||||
<channel id="repeat" typeId="repeat"/>
|
||||
<channel id="random" typeId="random"/>
|
||||
<channel id="track" typeId="track"/>
|
||||
<channel id="artist" typeId="artist"/>
|
||||
<channel id="album" typeId="album"/>
|
||||
<channel id="play_mode" typeId="music_play_mode"/>
|
||||
<channel id="play_speed" typeId="music_play_speed"/>
|
||||
<channel id="track_length" typeId="track_length"/>
|
||||
<channel id="track_position" typeId="track_position"/>
|
||||
<channel id="track_progress" typeId="track_progress"/>
|
||||
<channel id="track_handle" typeId="track_handle"/>
|
||||
<channel id="album_handle" typeId="album_handle"/>
|
||||
<channel id="nowplay_handle" typeId="nowplay_handle"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains metadata about a selected item</description>
|
||||
<channels>
|
||||
<channel id="type" typeId="type"/>
|
||||
<channel id="title" typeId="title"/>
|
||||
<channel id="album_title" typeId="album_title"/>
|
||||
<channel id="cover_art" typeId="cover_art"/>
|
||||
<channel id="cover_url" typeId="cover_url"/>
|
||||
<channel id="hires_cover_url" typeId="hires_cover_url"/>
|
||||
<channel id="rating" typeId="rating"/>
|
||||
<channel id="year" typeId="year"/>
|
||||
<channel id="running_time" typeId="running_time"/>
|
||||
<channel id="actors" typeId="actors"/>
|
||||
<channel id="artist" typeId="artist"/>
|
||||
<channel id="directors" typeId="directors"/>
|
||||
<channel id="genres" typeId="genres"/>
|
||||
<channel id="rating_reason" typeId="rating_reason"/>
|
||||
<channel id="synopsis" typeId="synopsis"/>
|
||||
<channel id="review" typeId="review"/>
|
||||
<channel id="color_description" typeId="color_description"/>
|
||||
<channel id="country" typeId="country"/>
|
||||
<channel id="aspect_ratio" typeId="aspect_ratio"/>
|
||||
<channel id="disc_location" typeId="disc_location"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<!-- Detail channels for Alto/Strato (remove music specific and disc vault channels) -->
|
||||
<channel-group-type id="alto-strato_detail">
|
||||
<label>Content Details</label>
|
||||
<description>Contains metadata about a selected item</description>
|
||||
<channels>
|
||||
<channel id="title" typeId="title"/>
|
||||
<channel id="cover_art" typeId="cover_art"/>
|
||||
<channel id="cover_url" typeId="cover_url"/>
|
||||
<channel id="hires_cover_url" typeId="hires_cover_url"/>
|
||||
<channel id="rating" typeId="rating"/>
|
||||
<channel id="year" typeId="year"/>
|
||||
<channel id="running_time" typeId="running_time"/>
|
||||
<channel id="actors" typeId="actors"/>
|
||||
<channel id="directors" typeId="directors"/>
|
||||
<channel id="rating_reason" typeId="rating_reason"/>
|
||||
<channel id="synopsis" typeId="synopsis"/>
|
||||
<channel id="color_description" typeId="color_description"/>
|
||||
<channel id="country" typeId="country"/>
|
||||
<channel id="aspect_ratio" typeId="aspect_ratio"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="movie_control">
|
||||
<item-type>Player</item-type>
|
||||
<label>Control</label>
|
||||
<description>Control movie playback e.g. Play/Pause/Next/Previous/Fast Forward/Rewind</description>
|
||||
<category>Player</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title_name">
|
||||
<item-type>String</item-type>
|
||||
<label>Title Name</label>
|
||||
<description>The title of the movie currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="movie_play_mode">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Mode</label>
|
||||
<description>The Current playback mode of the movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="movie_play_speed">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Speed</label>
|
||||
<description>The speed of playback scanning</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title_num" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Title Number</label>
|
||||
<description>The current movie title number that is playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title_length">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Title Length</label>
|
||||
<description>The total running time of the currently playing movie</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title_loc">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Title Location</label>
|
||||
<description>The running time elapsed of the currently playing movie</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chapter_num">
|
||||
<item-type>Number</item-type>
|
||||
<label>Chapter Number</label>
|
||||
<description>The current chapter number of the movie that is playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chapter_length">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Chapter Length</label>
|
||||
<description>The total running time of the current chapter</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chapter_loc">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Chapter Location</label>
|
||||
<description>The running time elapsed of the current chapter</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="movie_media_type">
|
||||
<item-type>String</item-type>
|
||||
<label>Media Type</label>
|
||||
<description>The type of media that is currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="movie_location">
|
||||
<item-type>String</item-type>
|
||||
<label>Movie Location</label>
|
||||
<description>Identifies the location in the movie, ie: Main Content, Intermission, or End Credits</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="aspect_ratio">
|
||||
<item-type>String</item-type>
|
||||
<label>Aspect Ratio</label>
|
||||
<description>Identifies the aspect ratio of the movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_mode" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Mode - Raw</label>
|
||||
<description>Raw output of video mode data from the component, format: 00:00:00</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_mode_composite">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Mode - Composite</label>
|
||||
<description>Identifies the video currently active on the Composite video output</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_mode_component">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Mode - Component</label>
|
||||
<description>Identifies the video currently active on the Component video output</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_mode_hdmi">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Mode - HDMI</label>
|
||||
<description>Identifies the video currently active on the HDMI video output</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_color" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Color</label>
|
||||
<description>Provides Color Information About the Current Video Output</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="video_color_eotf">
|
||||
<item-type>String</item-type>
|
||||
<label>Video Color EOTF</label>
|
||||
<description>Identifies the Electro-Optical Transfer Function standard of the current video output</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="content_color" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Content Color</label>
|
||||
<description>Provides color information about the currently playing content</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="content_color_eotf">
|
||||
<item-type>String</item-type>
|
||||
<label>Content Color EOTF</label>
|
||||
<description>Identifies the Electro-Optical Transfer Function standard of the currently playing content</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="scale_mode" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Scale Mode</label>
|
||||
<description>Identifies whether the image from the player requires scaling</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="screen_mask" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Screen Mask</label>
|
||||
<description>Provides aspect ratio and masking information for the current video image</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="screen_mask2" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Screen Mask 2</label>
|
||||
<description>Provides masking information based on aspect ratio and overscan area</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cinemascape_mask" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>CinemaScape Mask</label>
|
||||
<description>When in CinemaScape mode, provides information about the frame aspect ratio</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cinemascape_mode" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>CinemaScape Mode</label>
|
||||
<description>Identifies the CinemaScape mode currently active</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="ui_state" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>UI State</label>
|
||||
<description>Provides information about which screen is visible in the Kaleidescape user interface</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="child_mode_state" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Child Mode State</label>
|
||||
<description>Indicates if the on screen display is displaying the child user interface</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="readiness_state" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>System Readiness State</label>
|
||||
<description>Indicates the system's current idle mode</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="highlighted_selection" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Highlighted Selection</label>
|
||||
<description>Specifies the handle of the movie or album currently selected on the user interface</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="user_defined_event" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>User Defined Event</label>
|
||||
<description>Will contain custom event messages generated by scripts, sent from another component, or triggered by
|
||||
system events</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="user_input" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>User Input</label>
|
||||
<description>Indicates if the user is being prompted for input, what type of input, and any currently entered
|
||||
characters</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="user_input_prompt" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>User Input Prompt</label>
|
||||
<description>Indicates user input prompt info and properties currently shown on screen</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- music channels -->
|
||||
|
||||
<channel-type id="music_control">
|
||||
<item-type>Player</item-type>
|
||||
<label>Control</label>
|
||||
<description>Control music playback e.g. Play/Pause/Next/Previous/Fforward/Rewind</description>
|
||||
<category>Player</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="repeat">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Repeat</label>
|
||||
<description>Controls repeat playback for music</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="random">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Random</label>
|
||||
<description>Controls random playback for music</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track">
|
||||
<item-type>String</item-type>
|
||||
<label>Track</label>
|
||||
<description>The name of the currently playing track</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="artist">
|
||||
<item-type>String</item-type>
|
||||
<label>Artist</label>
|
||||
<description>The name of the currently playing artist</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album">
|
||||
<item-type>String</item-type>
|
||||
<label>Album</label>
|
||||
<description>The name of the currently playing album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="music_play_mode">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Mode</label>
|
||||
<description>The current playback mode of the music</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="music_play_speed">
|
||||
<item-type>String</item-type>
|
||||
<label>Play Speed</label>
|
||||
<description>The speed of playback scanning</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track_length">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Track Length</label>
|
||||
<description>The total running time of the current playing track</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track_position">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Track Position</label>
|
||||
<description>The running time elapsed of the current playing track</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track_progress">
|
||||
<item-type>Number</item-type>
|
||||
<label>Track Progress</label>
|
||||
<description>The percentage complete of the current playing track</description>
|
||||
<state readOnly="true" pattern="%d %%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track_handle" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Track Handle</label>
|
||||
<description>The handle of the currently playing track</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album_handle" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Album Handle</label>
|
||||
<description>The handle of the currently playing album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowplay_handle" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Now Playing Handle</label>
|
||||
<description>The handle of the current now playing list</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- metadata channels -->
|
||||
|
||||
<channel-type id="type">
|
||||
<item-type>String</item-type>
|
||||
<label>Detail Type</label>
|
||||
<description>Indicates If the currently selected item is a Movie or Album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="title">
|
||||
<item-type>String</item-type>
|
||||
<label>Movie Title</label>
|
||||
<description>The title of the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album_title">
|
||||
<item-type>String</item-type>
|
||||
<label>Album Title</label>
|
||||
<description>The title of the selected Album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cover_art">
|
||||
<item-type>Image</item-type>
|
||||
<label>Cover Art</label>
|
||||
<description>Cover Art image of the currently selected item</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="cover_url">
|
||||
<item-type>String</item-type>
|
||||
<label>Cover Art URL</label>
|
||||
<description>The URL of the Cover Art</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="hires_cover_url">
|
||||
<item-type>String</item-type>
|
||||
<label>HiRes Cover Art URL</label>
|
||||
<description>The URL of the high resolution Cover Art</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rating">
|
||||
<item-type>String</item-type>
|
||||
<label>Rating</label>
|
||||
<description>The MPAA rating of the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="year">
|
||||
<item-type>String</item-type>
|
||||
<label>Year</label>
|
||||
<description>The release year of the selected item</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="running_time">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Running time</label>
|
||||
<description>The total running time of the selected item</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="actors">
|
||||
<item-type>String</item-type>
|
||||
<label>Actors</label>
|
||||
<description>A list of actors appearing in the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="artist">
|
||||
<item-type>String</item-type>
|
||||
<label>Artist</label>
|
||||
<description>The artist of the selected Album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="directors">
|
||||
<item-type>String</item-type>
|
||||
<label>Directors</label>
|
||||
<description>A list of directors of the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="genres">
|
||||
<item-type>String</item-type>
|
||||
<label>Genres</label>
|
||||
<description>A list of genres of the selected item</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rating_reason">
|
||||
<item-type>String</item-type>
|
||||
<label>Rating Reason</label>
|
||||
<description>An explaination of why the selected movie received its rating</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="synopsis">
|
||||
<item-type>String</item-type>
|
||||
<label>Synopsis</label>
|
||||
<description>A synopsis of the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="review">
|
||||
<item-type>String</item-type>
|
||||
<label>Review</label>
|
||||
<description>A review of the selected Album</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color_description">
|
||||
<item-type>String</item-type>
|
||||
<label>Color Description</label>
|
||||
<description>Indicates if the selected Movie is in color, black and white, etc.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="country">
|
||||
<item-type>String</item-type>
|
||||
<label>Country</label>
|
||||
<description>The country that the selected Movie originates from</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="aspect_ratio">
|
||||
<item-type>String</item-type>
|
||||
<label>Aspect Ratio</label>
|
||||
<description>The aspect ratio of the selected Movie</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="disc_location">
|
||||
<item-type>String</item-type>
|
||||
<label>Disc Location</label>
|
||||
<description>Indicates where the disc for the selected item is currently residing in the system (ie Vault, Tray, etc.)</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user