added migrated 2.x add-ons

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

View File

@@ -0,0 +1,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>

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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);
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &amp; 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>

View File

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