added migrated 2.x add-ons

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,443 @@
# Kaleidescape Binding
![Kaleidescape logo](doc/Kaleidescape_Logo.png)
This binding is used to control and retrieve information from a Kaleidescape movie player.
All movie player components including the original K-Player series, M Class Players, Cinema One, Alto, and Strato are supported.
The 4 zone audio only KMUSIC-4000 is not supported at this time.
As there are many good control options already available for these components, this binding focuses primarily on retrieving information
for display purposes and to use in rules for controlling other Things such lighting, projector lens control, masking, etc.
Basic playback transport controls are provided and any other command that is supported by the control protocol can be sent to the component through rules based commands.
See [Kaleidescape-System-Control-Protocol-Reference-Manual.pdf](https://support.kaleidescape.com/article/Control-Protocol-Reference-Manual) for a reference of available commands.
To simplify the design of the binding code, a different Thing instance is created for each component
in a multi-zone system and each Thing maintains its own socket connection to the target component.
Overall this binding supports the majority of information and commands available in the Kaleidescape control protocol but is by no means exhaustive.
Any feedback or suggestions for improvement are welcome.
The binding supports two different kinds of connections:
* direct IP connection (preferred),
* serial connection (19200-8-N-1)
## Supported Things
There are four supported thing types, which represent the different models of Kaleidescape components.
It is important to choose the correct thing type to ensure the available channels are correct for the component being used.
The supported thing types are:
`player` Any KPlayer, M Class [M300, M500, M700] or Cinema One 1st Gen player
`cinemaone` Cinema One (2nd Gen)
`alto`
`strato` Includes Strato, Strato S, or Strato C
## Discovery
Manually initiated Auto-discovery is supported if Kaleidescape components are accessible on the same IP subnet of the openHAB server.
Since discovery involves scanning all IP addresses in the subnet range for an open socket, the discovery must be initiated by the user.
In the Inbox, select Search For Things and then choose the Kaleidescape System Binding to initiate discovery.
## Binding Configuration
There are no overall binding configuration settings that need to be set.
All settings are through thing configuration parameters.
## Thing Configuration
The thing has the following configuration parameters:
| Parameter Label | Parameter ID | Description | Accepted values |
|------------------------|---------------|------------------------------------------------------------------------------------|------------------------------------------------------|
| Address | host | Host name or IP address of the Kaleidescape component | A host name or IP address |
| Port | port | Communication port of the IP connection | 10000 (default - should not need to change) |
| Serial Port | serialPort | Serial port for connecting directly a component | Serial port name (optional) |
| Update Period | updatePeriod | Tells the component how often time status updates should be sent (see notes below) | 0 or 1 are the currently accepted values (default 0) |
| Volume Control Enabled | volumeEnabled | Enable the volume and mute controls in the K iPad & phone apps | Boolean (default false) |
| Initial Volume Setting | initialVolume | Initial volume level set when the binding starts up | 0 to 75 (default 25) |
Some notes:
* Due to a bug in the control protocol, a Strato C player will be identified as a Premiere 'Player' by the auto discovery process.
* The only caveat of note about this binding is the updatePeriod configuration parameter.
* When set to the default of 0, the component only sends running time update messages sporadically (as an example: when the movie chapter changes) while content is playing.
* In this case, the running time channels will also only sporadically update.
* When updatePeriod is set to 1 (values greater than 1 are not yet supported by the control protocol), the component sends running time status update messages every second.
* Be aware that this could cause performance impacts to your openHAB system.
* On Linux, you may get an error stating the serial port cannot be opened when the Kaleidescape binding tries to load.
* You can get around this by adding the `openhab` user to the `dialout` group like this: `usermod -a -G dialout openhab`.
* Also on Linux you may have issues with the USB if using two serial USB devices e.g. Kaleidescape and RFXcom.
* See the [general documentation about serial port configuration](/docs/administration/serial.html) for more on symlinking the USB ports.
## Channels
The following channels are available:
| Channel ID | Item Type | Description |
|----------------------------|-------------|------------------------------------------------------------------------------------------------------------------|
| ui#power | Switch | Turn the zone On or Off (system standby) |
| ui#volume | Dimmer | A virtual volume that tracks the volume in K control apps, use as a proxy to adjust a real volume item via rules |
| ui#mute | Switch | A virtual mute switch that tracks the mute status in K control apps, use as a proxy to control a real mute item |
| ui#control | Player | Control Movie Playback e.g. start/pause/next/previous/ffward/rewind |
| ui#title_name | String | The title of the movie currently playing |
| ui#play_mode | String | The current playback mode of the movie |
| ui#play_speed | String | The speed of playback scanning |
| ui#title_num | Number | The current movie title number that is playing |
| ui#title_length | Number:Time | The total running time of the currently playing movie (seconds) |
| ui#title_loc | Number:Time | The running time elapsed of the currently playing movie (seconds) |
| ui#chapter_num | Number | The current chapter number of the movie that is playing |
| ui#chapter_length | Number:Time | The total running time of the current chapter (seconds) |
| ui#chapter_loc | Number:Time | The running time elapsed of the current chapter |
| ui#movie_media_type | String | The type of media that is currently playing |
| ui#movie_location | String | Identifies the location in the movie, ie: Main content, Intermission, or End Credits |
| ui#aspect_ratio | String | Identifies the aspect ratio of the movie |
| ui#video_mode | String | Raw output of video mode data from the component, format: 00:00:00 |
| ui#video_mode_composite | String | Identifies the video mode currently active on the composite video output |
| ui#video_mode_component | String | Identifies the video mode currently active on the component video output |
| ui#video_mode_hdmi | String | Identifies the video mode currently active on the HDMI video output |
| ui#video_color | String | Provides color information about the current video output (Strato Only) |
| ui#video_color_eotf | String | Identifies the Electro-Optical Transfer Function standard of the current video output (Strato Only) |
| ui#content_color | String | Provides color information about the currently playing content (Strato Only) |
| ui#content_color_eotf | String | Identifies the Electro-Optical Transfer Function standard of the currently playing content (Strato Only) |
| ui#scale_mode | String | Identifies whether the image from the player requires scaling |
| ui#screen_mask | String | Provides aspect ratio and masking information for the current video image |
| ui#screen_mask2 | String | Provides masking information based on aspect ratio and overscan area |
| ui#cinemascape_mask | String | When in CinemaScape mode, provides information about the frame aspect ratio |
| ui#cinemascape_mode | String | Identifies the CinemaScape mode currently active |
| ui#ui_state | String | Provides information about which screen is visible in the Kaleidescape user interface |
| ui#child_mode_state | String | Indicates if the onscreen display is displaying the child user interface |
| ui#readiness_state | String | Indicates the system's current idle mode (Not available on Premiere system players) |
| ui#highlighted_selection | String | Specifies the handle of the movie or album currently selected on the user interface |
| ui#user_defined_event | String | Will contain custom event messages generated by scripts, sent from another component, or system events |
| ui#user_input | String | Indicates if the user is being prompted for input, what type of input, and any currently entered characters |
| ui#user_input_prompt | String | Indicates user input prompt info and properties currently shown on screen |
| -- music channels (not available on Alto and Strato) -- |
| music#control | Player | Control Music Playback e.g. start/pause/next/previous/ffward/rewind |
| music#repeat | Switch | Controls repeat playback for music |
| music#random | Switch | Controls random playback for music |
| music#track | String | The name of the currently playing track |
| music#artist | String | The name of the currently playing artist |
| music#album | String | The name of the currently playing album |
| music#play_mode | String | The current playback mode of the music |
| music#play_speed | String | The speed of playback scanning |
| music#track_length | Number:Time | The total running time of the current playing track (seconds) |
| music#track_position | Number:Time | The running time elapsed of the current playing track (seconds) |
| music#track_progress | Number | The percentage complete of the current playing track |
| music#track_handle | String | The handle of the currently playing track |
| music#album_handle | String | The handle of the currently playing album |
| music#nowplay_handle | String | The handle of the current now playing list |
| -- metadata display channels (music related channels not available on Alto and Strato) -- |
| detail#type | String | Indicates if the currently selected item is a Movie or Album |
| detail#title | String | The title of the selected movie |
| detail#album_title | String | The title of the selected album |
| detail#cover_art | Image | Cover art image of the currently selected item |
| detail#cover_url | String | The url of the cover art |
| detail#hires_cover_url | String | The url of the high resolution cover art |
| detail#rating | String | The MPAA rating of the selected movie |
| detail#year | String | The release year of the selected item |
| detail#running_time | Number:Time | The total running time of the selected item (seconds) |
| detail#actors | String | A list of actors appearing in the selected movie |
| detail#artist | String | The artist of the selected album |
| detail#directors | String | A list of directors of the selected movie |
| detail#genres | String | A list of genres of the selected item |
| detail#rating_reason | String | An explaination of why the selected movie received its rating |
| detail#synopsis | String | A synopsis of the selected movie |
| detail#review | String | A review of the selected album |
| detail#color_description | String | Indicates if the selected movie is in Color, Black and White, etc. |
| detail#country | String | The country that the selected movie originates from |
| detail#aspect_ratio | String | The aspect ratio of the selected movie |
| detail#disc_location | String | Indicates where the disc for the selected item is currently residing in the system (ie Vault, Tray, etc.) |
## Full Example
kaleidescape.things:
```java
kaleidescape:player:myzone1 "M500 Living Rm" [host="192.168.1.10", updatePeriod=0, volumeEnabled=true, initialVolume=20]
kaleidescape:cinemaone:myzone2 "My Cinema One" [host="192.168.1.11", updatePeriod=0, volumeEnabled=true, initialVolume=20]
```
kaleidescape.items:
```java
// Virtual switch to send a command, see sitemap and rules below
Switch z1_GoMovieCovers "Go to Movie Covers"
// Movie Channels
Switch z1_Ui_Power "Power" { channel="kaleidescape:player:myzone1:ui#power" }
Dimmer z1_Ui_Volume "Volume" { channel="kaleidescape:player:myzone1:ui#volume" }
Switch z1_Ui_Mute "Mute" { channel="kaleidescape:player:myzone1:ui#mute" }
Player z1_Ui_Control "Control" { channel="kaleidescape:player:myzone1:ui#control" }
String z1_Ui_TitleName "Movie Title: [%s]" { channel="kaleidescape:player:myzone1:ui#title_name" }
String z1_Ui_PlayMode "Play Mode: [%s]" { channel="kaleidescape:player:myzone1:ui#play_mode" }
String z1_Ui_PlaySpeed "Play Speed: [%s]" { channel="kaleidescape:player:myzone1:ui#play_speed" }
Number z1_Ui_TitleNum "Title Number: [%s]" { channel="kaleidescape:player:myzone1:ui#title_num" }
Number:Time z1_Ui_TitleLength "Title Length: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:ui#title_length" }
Number:Time z1_Ui_TitleLoc "Title Location: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:ui#title_loc" }
Number z1_Ui_ChapterNum "Chapter Number: [%s]" { channel="kaleidescape:player:myzone1:ui#chapter_num" }
Number:Time z1_Ui_ChapterLength "Chapter Length: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:ui#chapter_length" }
Number:Time z1_Ui_ChapterLoc "Chapter Location: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:ui#chapter_loc" }
String z1_Ui_MovieMediaType "Media Type: [%s]" { channel="kaleidescape:player:myzone1:ui#movie_media_type" }
String z1_Ui_MovieLocation "Movie Location: [%s]" { channel="kaleidescape:player:myzone1:ui#movie_location" }
String z1_Ui_AspectRatio "Aspect Ratio: [%s]" { channel="kaleidescape:player:myzone1:ui#aspect_ratio" }
String z1_Ui_VideoMode "Video Mode (raw): [%s]" { channel="kaleidescape:player:myzone1:ui#video_mode" }
String z1_Ui_VideoModeComposite "Video Mode (Composite): [%s]" { channel="kaleidescape:player:myzone1:ui#video_mode_composite" }
String z1_Ui_VideoModeComponent "Video Mode (Component): [%s]" { channel="kaleidescape:player:myzone1:ui#video_mode_component" }
String z1_Ui_VideoModeHdmi "Video Mode (HDMI): [%s]" { channel="kaleidescape:player:myzone1:ui#video_mode_hdmi" }
// Video Color and Content Color only available on the Strato
String z1_Ui_VideoColor "Video Color: [%s]" { channel="kaleidescape:player:myzone1:ui#video_color" }
String z1_Ui_VideoColorEotf "Video Color EOTF: [%s]" { channel="kaleidescape:player:myzone1:ui#video_color_eotf" }
String z1_Ui_ContentColor "Content Color: [%s]" { channel="kaleidescape:player:myzone1:ui#content_color" }
String z1_Ui_ContentColorEotf "Content Color EOTF: [%s]" { channel="kaleidescape:player:myzone1:ui#content_color_eotf" }
String z1_Ui_ScaleMode "Scale Mode: [%s]" { channel="kaleidescape:player:myzone1:ui#scale_mode" }
String z1_Ui_ScreenMask "Screen Mask: [%s]" { channel="kaleidescape:player:myzone1:ui#screen_mask" }
String z1_Ui_ScreenMask2 "Screen Mask 2: [%s]" { channel="kaleidescape:player:myzone1:ui#screen_mask2" }
String z1_Ui_CinemascapeMask "CinemaScape Mask: [%s]" { channel="kaleidescape:player:myzone1:ui#cinemascape_mask" }
String z1_Ui_CinemascapeMode "CinemaScape Mode: [%s]" { channel="kaleidescape:player:myzone1:ui#cinemascape_mode" }
String z1_Ui_UiState "UI State: [%s]" { channel="kaleidescape:player:myzone1:ui#ui_state" }
String z1_Ui_ChildModeState "Child Mode State: [%s]" { channel="kaleidescape:player:myzone1:ui#child_mode_state" }
String z1_Ui_ReadinessState "Readiness State: [%s]" { channel="kaleidescape:player:myzone1:ui#readiness_state" }
String z1_Ui_HighlightedSelection "Highlighted Selection: [%s]" { channel="kaleidescape:player:myzone1:ui#highlighted_selection" }
String z1_Ui_UserDefinedEvent "User Defined Event: [%s]" { channel="kaleidescape:player:myzone1:ui#user_defined_event" }
String z1_Ui_UserInput "User Input: [%s]" { channel="kaleidescape:player:myzone1:ui#user_input" }
String z1_Ui_UserInputPrompt "User Input Prompt[%s]" { channel="kaleidescape:player:myzone1:ui#user_input_prompt" }
// Music Channels (not available on Alto or Strato)
Player z1_Music_Control "Music Control" { channel="kaleidescape:player:myzone1:music#control" }
Switch z1_Music_Repeat "Repeat" { channel="kaleidescape:player:myzone1:music#repeat" }
Switch z1_Music_Random "Random" { channel="kaleidescape:player:myzone1:music#random" }
String z1_Music_Track "Track: [%s]" { channel="kaleidescape:player:myzone1:music#track" }
String z1_Music_Artist "Artist: [%s]" { channel="kaleidescape:player:myzone1:music#artist" }
String z1_Music_Album "Album: [%s]" { channel="kaleidescape:player:myzone1:music#album" }
String z1_Music_PlayMode "Play Mode: [%s]" { channel="kaleidescape:player:myzone1:music#play_mode" }
String z1_Music_PlaySpeed "Play Speed: [%s]" { channel="kaleidescape:player:myzone1:music#play_speed" }
Number:Time z1_Music_TrackLength "Track Length: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:music#track_length" }
Number:Time z1_Music_TrackPosition "Track Position: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:music#track_position" }
Number z1_Music_TrackProgress "Track Progress: [%s %%]" { channel="kaleidescape:player:myzone1:music#track_progress" }
String z1_Music_TrackHandle "Track Handle: [%s]" { channel="kaleidescape:player:myzone1:music#track_handle" }
String z1_Music_AlbumHandle "Album Handle: [%s]" { channel="kaleidescape:player:myzone1:music#album_handle" }
String z1_Music_NowplayHandle "Now Playing Handle: [%s]" { channel="kaleidescape:player:myzone1:music#nowplay_handle" }
// Metatdata Display Channels (Album Title, Artist & Review are not available on Alto or Strato)
String z1_Detail_Type "Metadata type: [%s]" { channel="kaleidescape:player:myzone1:detail#type" }
String z1_Detail_Title "Title: [%s]" { channel="kaleidescape:player:myzone1:detail#title" }
String z1_Detail_AlbumTitle "Album: [%s]" { channel="kaleidescape:player:myzone1:detail#album_title" }
Image z1_Detail_CoverArt { channel="kaleidescape:player:myzone1:detail#cover_art" }
String z1_Detail_CoverUrl "[%s]" { channel="kaleidescape:player:myzone1:detail#cover_url" }
String z1_Detail_HiresCoverUrl "[%s]" { channel="kaleidescape:player:myzone1:detail#hires_cover_url" }
String z1_Detail_Rating "Rating: [%s]" { channel="kaleidescape:player:myzone1:detail#rating" }
String z1_Detail_Year "Year: [%s]" { channel="kaleidescape:player:myzone1:detail#year" }
Number:Time z1_Detail_RunningTime "Running Time: [JS(ksecondsformat.js):%s]" { channel="kaleidescape:player:myzone1:detail#running_time" }
String z1_Detail_Actors "Actors: [%s]" { channel="kaleidescape:player:myzone1:detail#actors" }
String z1_Detail_Directors "Directors: [%s]" { channel="kaleidescape:player:myzone1:detail#directors" }
String z1_Detail_Artist "Artist: [%s]" { channel="kaleidescape:player:myzone1:detail#artist" }
String z1_Detail_Genres "Genres: [%s]" { channel="kaleidescape:player:myzone1:detail#genres" }
String z1_Detail_RatingReason "Rating Reason: [%s]" { channel="kaleidescape:player:myzone1:detail#rating_reason" }
String z1_Detail_Synopsis "Synopsis: [%s]" { channel="kaleidescape:player:myzone1:detail#synopsis" }
String z1_Detail_Review "Review: [%s]" { channel="kaleidescape:player:myzone1:detail#review" }
String z1_Detail_ColorDescription "Color Description: [%s]" { channel="kaleidescape:player:myzone1:detail#color_description" }
String z1_Detail_Country "Country: [%s]" { channel="kaleidescape:player:myzone1:detail#country" }
String z1_Detail_AspectRatio "Aspect Ratio: [%s]" { channel="kaleidescape:player:myzone1:detail#aspect_ratio" }
String z1_Detail_DiscLocation "Disc Location: [%s]" { channel="kaleidescape:player:myzone1:detail#disc_location" }
```
ksecondsformat.js:
```java
(function(totalSeconds) {
if (isNaN(totalSeconds)) {
return '-';
} else {
hours = Math.floor(totalSeconds / 3600);
totalSeconds %= 3600;
minutes = Math.floor(totalSeconds / 60);
seconds = totalSeconds % 60;
if ( minutes < 10 ) {
minutes = '0' + minutes;
}
if ( seconds < 10 ) {
seconds = '0' + seconds;
}
return hours + ':' + minutes + ':' + seconds;
}
})(input)
```
kaleidescape.sitemap:
```perl
sitemap kaleidescape label="Kaleidescape" {
Frame label="Zone 1" {
Image item=z1_Detail_CoverArt
Text item=z1_Detail_Title visibility=[z1_Detail_Type=="movie"] icon="video"
Text item=z1_Detail_Artist visibility=[z1_Detail_Type=="album"] icon="microphone"
Text item=z1_Detail_AlbumTitle visibility=[z1_Detail_Type=="album"] icon="soundvolume-0"
Text item=z1_Detail_Rating visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Year visibility=[z1_Detail_Type=="movie", z1_Detail_Type=="album"] icon="none"
Text item=z1_Detail_RunningTime visibility=[z1_Detail_Type=="movie", z1_Detail_Type=="album"] icon="time"
Text item=z1_Detail_Actors visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Directors visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Genres visibility=[z1_Detail_Type=="movie", z1_Detail_Type=="album"] icon="none"
Text item=z1_Detail_RatingReason visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Synopsis visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Review visibility=[z1_Detail_Type=="album"] icon="none"
Text item=z1_Detail_ColorDescription visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_Country visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_AspectRatio visibility=[z1_Detail_Type=="movie"] icon="none"
Text item=z1_Detail_DiscLocation visibility=[z1_Detail_Type=="movie", z1_Detail_Type=="album"] icon="player"
Text label="Now Playing - Movie" icon="screen" {
Switch item=z1_Ui_Power
Slider item=z1_Ui_Volume
Switch item=z1_Ui_Mute
Default item=z1_Ui_Control
Switch item=z1_GoMovieCovers mappings=[ON="Movie Covers"]
Text item=z1_Ui_TitleName icon="video"
Text item=z1_Ui_PlayMode icon="player"
Text item=z1_Ui_PlaySpeed icon="player"
Text item=z1_Ui_TitleNum icon="video"
Text item=z1_Ui_TitleLength icon="time"
Text item=z1_Ui_TitleLoc icon="time"
Text item=z1_Ui_MovieMediaType icon="colorwheel"
Text item=z1_Ui_ChapterNum icon="video"
Text item=z1_Ui_ChapterLength icon="time"
Text item=z1_Ui_ChapterLoc icon="time"
Text item=z1_Ui_MovieLocation icon="video"
Text item=z1_Ui_AspectRatio icon="cinemascreen"
Text item=z1_Ui_VideoMode icon="screen"
Text item=z1_Ui_VideoModeComposite icon="screen"
Text item=z1_Ui_VideoModeComponent icon="screen"
Text item=z1_Ui_VideoModeHdmi icon="screen"
Text item=z1_Ui_VideoColor icon="screen"
Text item=z1_Ui_VideoColorEotf icon="screen"
Text item=z1_Ui_ContentColor icon="screen"
Text item=z1_Ui_ContentColorEotf icon="screen"
Text item=z1_Ui_ScaleMode icon="screen"
Text item=z1_Ui_ScreenMask icon="screen"
Text item=z1_Ui_ScreenMask2 icon="screen"
Text item=z1_Ui_CinemascapeMask icon="screen"
Text item=z1_Ui_CinemascapeMode icon="screen"
Text item=z1_Ui_UiState icon="player"
Text item=z1_Ui_ChildModeState icon="player"
Text item=z1_Ui_ReadinessState icon="switch"
Text item=z1_Ui_HighlightedSelection icon="zoom"
Text item=z1_Ui_UserDefinedEvent icon="zoom"
Text item=z1_Ui_UserInput icon="zoom"
Text item=z1_Ui_UserInputPrompt icon="zoom"
}
Text label="Now Playing - Music" icon="soundvolume-0" {
Switch item=z1_Ui_Power
Slider item=z1_Ui_Volume
Switch item=z1_Ui_Mute
Default item=z1_Music_Control
Switch item=z1_Music_Repeat
Switch item=z1_Music_Random
Text item=z1_Music_Track icon="soundvolume-0"
Text item=z1_Music_Artist icon="microphone"
Text item=z1_Music_Album icon="soundvolume-0"
Text item=z1_Music_PlayMode icon="player"
Text item=z1_Music_PlaySpeed icon="player"
Text item=z1_Music_TrackLength icon="time"
Text item=z1_Music_TrackPosition icon="time"
Text item=z1_Music_TrackProgress icon="time"
Text item=z1_Music_TrackHandle icon="zoom"
Text item=z1_Music_AlbumHandle icon="zoom"
Text item=z1_Music_NowplayHandle icon="zoom"
}
}
}
```
kaleidescape.rules:
```java
var int lightPercent
val kactions = getActions("kaleidescape","kaleidescape:player:myzone1")
// send command to go to movie covers when button pressed
rule "Go to Movie Covers"
when
Item z1_GoMovieCovers received command
then
if(null === kactions) {
logInfo("kactions", "Actions not found, check thing ID")
return
}
kactions.sendKCommand("GO_MOVIE_COVERS")
end
// send command to play a script
rule "Play Script - Great Vistas"
when
Item z1_PlayScript received command
then
if(null === kactions) {
logInfo("kactions", "Actions not found, check thing ID")
return
}
kactions.sendKCommand("PLAY_SCRIPT:Great Vistas")
end
// handle a control system command sent from a script
rule "Handle script commands"
when
Item z1_Ui_UserDefinedEvent received update
then
if (z1_Ui_UserDefinedEvent.state.toString == "DO_THE_NEEDFUL") {
logInfo("k rules", "handing the NEEDFUL script command...")
}
end
rule "Load selected item Metadata"
when
Item z1_Ui_HighlightedSelection changed
then
if(null === kactions) {
logInfo("kactions", "Actions not found, check thing ID")
return
}
kactions.sendKCommand("GET_CONTENT_DETAILS:" + z1_Ui_HighlightedSelection.state.toString + ":")
end
rule "Load Metadata for currently playing album"
when
Item z1_Music_AlbumHandle changed
then
if(null === kactions) {
logInfo("kactions", "Actions not found, check thing ID")
return
}
kactions.sendKCommand("GET_CONTENT_DETAILS:" + z1_Music_AlbumHandle.state.toString + ":")
end
rule "Bring up Lights when movie is over"
when
Item z1_Ui_MovieLocation changed from "Main content" to "End Credits"
then
// fade the lights up slowly while the credits are rolling
lightPercent = 0
while (lightPercent < 100) {
lightPercent = lightPercent + 5
logInfo("k rules", "lights at " + lightPercent.toString + " percent")
//myLightItem.sendCommand(lightPercent)
Thread::sleep(5000)
}
end
rule "Bring up Lights at 20 percent during intermission"
when
Item z1_Ui_MovieLocation changed from "Main content" to "Intermission"
then
//myLightItem.sendCommand(20)
logInfo("k rules", "intermission started")
end
rule "Turn lights back off when intermission over"
when
Item z1_Ui_MovieLocation changed from "Intermission" to "Main content"
then
//myLightItem.sendCommand(OFF)
logInfo("k rules", "intermission over")
end
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

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

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>