]> git.basschouten.com Git - openhab-addons.git/commitdiff
[panasonicbdp] Initial contribution (#16122)
authormlobstein <michael.lobstein@gmail.com>
Mon, 1 Jan 2024 19:54:35 +0000 (13:54 -0600)
committerGitHub <noreply@github.com>
Mon, 1 Jan 2024 19:54:35 +0000 (20:54 +0100)
Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
16 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.panasonicbdp/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/README.md [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/discovery/PanaBlurayDiscoveryParticipant.java [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/handler/PanaBlurayHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/i18n/panasonicbdp.properties [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/bdplayer.xml [new file with mode: 0644]
bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/uhdplayer.xml [new file with mode: 0644]
bundles/pom.xml

index 59a56ddd9f1d3375bda5de07002bbec8b4a96fcc..79a33f3c15eb1eb29779452c55b1cf611dc75c14 100644 (file)
 /bundles/org.openhab.binding.oppo/ @mlobstein
 /bundles/org.openhab.binding.orbitbhyve/ @octa22
 /bundles/org.openhab.binding.orvibo/ @tavalin
+/bundles/org.openhab.binding.panasonicbdp/ @mlobstein
 /bundles/org.openhab.binding.paradoxalarm/ @theater
 /bundles/org.openhab.binding.pentair/ @jsjames
 /bundles/org.openhab.binding.phc/ @gnlpfjh
index dd5cb09900a0aa7960dece07bbb8917a2ee6bd4f..44ad3d72de5b15f627cde4de26a296467054a835 100644 (file)
       <artifactId>org.openhab.binding.orvibo</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.panasonicbdp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.paradoxalarm</artifactId>
diff --git a/bundles/org.openhab.binding.panasonicbdp/NOTICE b/bundles/org.openhab.binding.panasonicbdp/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -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
diff --git a/bundles/org.openhab.binding.panasonicbdp/README.md b/bundles/org.openhab.binding.panasonicbdp/README.md
new file mode 100644 (file)
index 0000000..0eb3ef1
--- /dev/null
@@ -0,0 +1,242 @@
+# Panasonic Blu-ray Player Binding
+
+This binding connects Panasonic Blu-ray players from 2011/2012 and UHD Blu-ray players from 2018 to openHAB.  
+**Supported Blu-ray models:** DMP-BDT110, DMP-BDT210, DMP-BDT310, DMP-BDT120, DMP-BDT220, DMP-BDT320, DMP-BBT01 & DMP-BDT500.  
+**Supported UHD models:** DP-UB420/424, DP-UB820/824 & DP-UB9000/9004.  
+
+**Please note:** The player must be on the same IP subnet as the openHAB server for this binding to function.
+If the connection to the player originates from a different subnet, 404 response errors are sent in response to all requests.
+
+To enable network remote control of the Blu-ray model players, configure the following settings:  
+**Player Settings/Network/Network Settings/Remote Device Settings**  
+Then make sure you have the following values set:  
+**Remote Device Operation: On**  
+**Registration Type: Automatic**  
+
+For the UHD models, Voice Control must be enabled for the player's http interface to be active:
+**Player Settings/Network/Voice Control: On**
+
+To enable the binding to control the player while off (network active while off), Quick Start mode must be On as follows:  
+**Player Settings/System/Quick Start: On**  
+
+**UHD Model Command Authentication:**  
+The UHD models require authentication to use the control API.
+A player key must be specified in the thing configuration in order for the `power`, `button` and `control` channels to work.
+UHD model players that are patched do not require a player key.
+See the [AVForums discussions](https://www.avforums.com/forums/blu-ray-dvd-player-multiregion-hacks.126/) of the DP-UB420/820/9000 players for more information.
+
+## Supported Things
+
+There are two supported thing types, which represent either a BD player or a UHD player.
+A supported Blu-ray player uses the `bd-player` id and a supported UHD Blu-ray player uses the `uhd-player` id.
+Multiple Things can be added if more than one player is to be controlled.
+
+## Discovery
+
+Auto-discovery is supported if the player can be located on the local network using UPnP.
+Otherwise the thing must be manually added.
+
+## Binding Configuration
+
+The binding has no configuration options, all configuration is done at Thing level.
+
+## Thing Configuration
+
+The Thing has a few configuration parameters:
+
+| Name      | Type    | Description                                                                                           | Default | Required |
+|-----------|---------|-------------------------------------------------------------------------------------------------------|---------|----------|
+| hostName  | text    | The host name or IP address of the player.                                                            | N/A     | yes      |
+| refresh   | integer | Overrides the refresh interval of the player status. Minimum interval is 5 seconds.                   | 5       | no       |
+| playerKey | text    | For UHD models, to enable authentication of control commands, a key for the player must be specified. | N/A     | no       |
+
+Some notes:
+
+* The control protocol of these players is undocumented and may not work consistently in all situations
+* The UHD models only support playback elapsed time (not title total time or chapter information) reporting
+* The time and chapter information is only available when playing a Blu-ray disc (not DVD or CD)
+* There are reports in forum postings that not all commands work on all of the older models (i.e.: Power does not work on DMP-BDT110)
+* Not all status information is available from all BD models (i.e.: playback elapsed time not reported by some models)
+
+## Channels
+
+The following channels are available:
+
+| Channel ID      | Item Type   | Read/Write | Description                                                                           |
+|-----------------|-------------|------------|---------------------------------------------------------------------------------------|
+| power           | Switch      | RW         | Turn the power for the player ON or OFF.                                              |
+| button          | String      | W          | Sends a command to the player. See lists of available commands in Appendix A below.   |
+| control         | Player      | RW         | Control Playback e.g. Play/Pause/Next/Previous/FForward/Rewind.                       |
+| player-status   | String      | R          | The player status i.e.: Power Off, Tray Open, Stopped, Playback, Pause Playback, etc. |
+| time-elapsed    | Number:Time | R          | The total number of seconds of playback time elapsed.                                 |
+| time-total      | Number:Time | R          | The total length of the current playing title in seconds. Not on UHD models.          |
+| chapter-current | Number      | R          | The current chapter number. Not on UHD models.                                        |
+| chapter-total   | Number      | R          | The total number of chapters in the current title. Not on UHD models.                 |
+
+## Full Example
+
+panasonicbdp.things:
+
+```java
+panasonicbdp:bd-player:mybdplayer "My Blu-ray player" [ hostName="192.168.10.1", refresh=5 ]
+panasonicbdp:uhd-player:myuhdplayer "My UHD Blu-ray player" [ hostName="192.168.10.1", refresh=5, playerKey="ABCDEF1234567890abcdef0123456789" ]
+```
+
+panasonicbdp.items:
+
+```java
+// BD Player
+Switch Player_Power            "Power"                     { channel="panasonicbdp:bd-player:mybdplayer:power" }
+String Player_Button           "Send Command"              { channel="panasonicbdp:bd-player:mybdplayer:button", autoupdate="false" }
+Player Player_Control          "Control"                   { channel="panasonicbdp:bd-player:mybdplayer:control" }
+String Player_PlayerStatus     "Status: [%s]"              { channel="panasonicbdp:bd-player:mybdplayer:player-status" }
+Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="panasonicbdp:bd-player:mybdplayer:time-elapsed" }
+Number:Time Player_TimeTotal   "Total Time: [%d %unit%]"   { channel="panasonicbdp:bd-player:mybdplayer:time-total" }
+Number Player_ChapterCurrent   "Current Chapter: [%d]"     { channel="panasonicbdp:bd-player:mybdplayer:chapter-current" }
+Number Player_ChapterTotal     "Total Chapters: [%d]"      { channel="panasonicbdp:bd-player:mybdplayer:chapter-total" }
+
+// UHD Player
+Switch Player_Power            "Power"                     { channel="panasonicbdp:uhd-player:myuhdplayer:power" }
+String Player_Button           "Send Command"              { channel="panasonicbdp:uhd-player:myuhdplayer:button", autoupdate="false" }
+Player Player_Control          "Control"                   { channel="panasonicbdp:uhd-player:myuhdplayer:control" }
+String Player_PlayerStatus     "Status: [%s]"              { channel="panasonicbdp:uhd-player:myuhdplayer:player-status" }
+Number:Time Player_TimeElapsed "Elapsed Time: [%d %unit%]" { channel="panasonicbdp:uhd-player:myuhdplayer:time-elapsed" }
+```
+
+panasonicbdp.sitemap:
+
+```perl
+sitemap panasonicbdp label="Panasonic Blu-ray" {
+    Frame label="Blu-ray Player" {
+        Switch item=Player_Power
+        Selection item=Player_Button icon="player"
+        Default item=Player_Control
+        Text item=Player_PlayerStatus
+        Text item=Player_TimeElapsed icon="time"
+        // The following three channels are not available on UHD models
+        Text item=Player_TimeTotal icon="time"
+        Text item=Player_ChapterCurrent icon="time"
+        Text item=Player_ChapterTotal icon="time"
+    }
+}
+```
+
+### Appendix A - 'button' channel command codes:
+
+**List of available button commands for BD players:**  
+
+| Function                 | Command  |
+|--------------------------|----------|
+| Power On                 | POWERON  |
+| Power Off                | POWEROFF |
+| Power Toggle             | POWER    |
+| Play                     | PLAYBACK |
+| Pause                    | PAUSE    |
+| Stop                     | STOP     |
+| Fast Forward             | CUE      |
+| Reverse                  | REV      |
+| Skip Forward             | SKIPFWD  |
+| Skip Back                | SKIPREV  |
+| Open/Close               | OP_CL    |
+| Status                   | DSPSEL   |
+| Top Menu                 | TITLE    |
+| Pop-Up Menu              | PUPMENU  |
+| Up                       | UP       |
+| Down                     | DOWN     |
+| Left                     | LEFT     |
+| Right                    | RIGHT    |
+| OK                       | SELECT   |
+| Submenu                  | MENU     |
+| Return                   | RETURN   |
+| 1 (@.)                   | D1       |
+| 2 (ABC)                  | D2       |
+| 3 (DEF)                  | D3       |
+| 4 (GHI)                  | D4       |
+| 5 (JKL)                  | D5       |
+| 6 (MNO)                  | D6       |
+| 7 (PQRS)                 | D7       |
+| 8 (TUV)                  | D8       |
+| 9 (WXYZ)                 | D9       |
+| 0 (-,)                   | D0       |
+| 12                       | D12      |
+| &#42; (Cancel)           | CLEAR    |
+| &#35; ([_])              | SHARP    |
+| Red                      | RED      |
+| Green                    | GREEN    |
+| Blue                     | BLUE     |
+| Yellow                   | YELLOW   |
+| Home                     | MLTNAVI  |
+| Netflix (broken/too old) | NETFLIX  |
+| VIERA Cast               | V_CAST   |
+| Network                  | NETWORK  |
+| Setup                    | SETUP    |
+| Exit                     | EXIT     |
+| Audio                    | AUDIOSEL |
+| 3D                       | 3D       |
+| Playback View (buttons not in other views)||
+| PIP                      | P_IN_P   |
+| OSD (DISPLAY)            | OSDONOFF |
+| Shuttle(BD) View (buttons not in other views)||
+| Swipe in CW circle       | SHFWD2   |
+| Swipe in CCW circle      | SHREV2   | 
+
+**List of available button commands for UHD players:**  
+
+| Function         | Command          |
+|------------------|------------------|
+| Power On         | POWERON          |
+| Power Off        | POWEROFF         |
+| Power Toggle     | POWER            |
+| Play             | PLAYBACK         |
+| Pause            | PAUSE            |
+| Stop             | STOP             |
+| Fast Forward     | CUE              |
+| Reverse          | REV              |
+| Skip Forward     | SKIPFWD          |
+| Skip Back        | SKIPREV          |
+| Manual Skip +60s | MNSKIP           |
+| Manual Skip -10s | MNBACK           |
+| Open/Close       | OP_CL            |
+| Status           | DSPSEL           |
+| Top Menu         | TITLE            |
+| Pop-up Menu      | PUPMENU          |
+| Up               | UP               |
+| Down             | DOWN             |
+| Left             | LEFT             |
+| Right            | RIGHT            |
+| OK               | SELECT           |
+| Submenu          | MENU             |
+| Return           | RETURN           |
+| 1 (@.)           | D1               |
+| 2 (ABC)          | D2               |
+| 3 (DEF)          | D3               |
+| 4 (GHI)          | D4               |
+| 5 (JKL)          | D5               |
+| 6 (MNO)          | D6               |
+| 7 (PQRS)         | D7               |
+| 8 (TUV)          | D8               |
+| 9 (WXYZ)         | D9               |
+| 0 (-,)           | D0               |
+| 12               | D12              |
+| &#42; (Cancel)   | CLEAR            |
+| &#35; ([_])      | SHARP            |
+| Red              | RED              |
+| Green            | GREEN            |
+| Blue             | BLUE             |
+| Yellow           | YELLOW           |
+| Home             | MLTNAVI          |
+| Netflix          | NETFLIX          |
+| VIERA Cast       | V_CAST           |
+| Network          | NETWORK          |
+| Setup            | SETUP            |
+| Exit             | EXIT             |
+| Audio            | AUDIOSEL         |
+| Subtitle         | TITLEONOFF       |
+| Closed Caption   | CLOSED_CAPTION   |
+| Playback Info    | PLAYBACKINFO     |
+| HDR Picture Mode | HDR_PICTUREMODE  |
+| Mirroring        | MIRACAST         |
+| Picture Setting  | PICTURESETTINGS  |
+| Sound Effect     | SOUNDEFFECT      |
+| High Clarity     | HIGHCLARITY      |
+| Skip The Trailer | SKIP_THE_TRAILER |
diff --git a/bundles/org.openhab.binding.panasonicbdp/pom.xml b/bundles/org.openhab.binding.panasonicbdp/pom.xml
new file mode 100644 (file)
index 0000000..31e6862
--- /dev/null
@@ -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>4.2.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.panasonicbdp</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Panasonic Blu-ray Player Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/feature/feature.xml b/bundles/org.openhab.binding.panasonicbdp/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..1fcdbde
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.panasonicbdp-${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-panasonicbdp" description="Panasonic Blu-ray Player Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <feature>openhab-transport-upnp</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.panasonicbdp/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayBindingConstants.java b/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayBindingConstants.java
new file mode 100644 (file)
index 0000000..580887d
--- /dev/null
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2010-2024 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.panasonicbdp.internal;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.measure.Unit;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.util.Fields;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link PanaBlurayBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class PanaBlurayBindingConstants {
+    public static final String BINDING_ID = "panasonicbdp";
+    public static final String PROPERTY_UUID = "uuid";
+    public static final String PROPERTY_HOST_NAME = "hostName";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_BD_PLAYER = new ThingTypeUID(BINDING_ID, "bd-player");
+    public static final ThingTypeUID THING_TYPE_UHD_PLAYER = new ThingTypeUID(BINDING_ID, "uhd-player");
+
+    // List of all Channel id's
+    public static final String POWER = "power";
+    public static final String BUTTON = "button";
+    public static final String CONTROL = "control";
+    public static final String PLAYER_STATUS = "player-status";
+    public static final String TIME_ELAPSED = "time-elapsed";
+    public static final String TIME_TOTAL = "time-total";
+    public static final String CHAPTER_CURRENT = "chapter-current";
+    public static final String CHAPTER_TOTAL = "chapter-total";
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BD_PLAYER,
+            THING_TYPE_UHD_PLAYER);
+
+    // Units of measurement of the data delivered by the API
+    public static final Unit<Time> API_SECONDS_UNIT = Units.SECOND;
+
+    public static final int DEFAULT_REFRESH_PERIOD_SEC = 5;
+    public static final String USER_AGENT = "MEI-LAN-REMOTE-CALL";
+    public static final String SHA_256_ALGORITHM = "SHA-256";
+    public static final String CRLF = "\r\n";
+    public static final String COMMA = ",";
+    public static final String STOP_STATUS = "00";
+    public static final String OPEN_STATUS = "01";
+    public static final String OFF_STATUS = "07";
+    public static final String PLAY_STATUS = "08";
+    public static final String PAUSE_STATUS = "09";
+    public static final String EMPTY = "";
+    public static final String PLAYER_CMD_ERR = "52,\"";
+
+    public static final String CMD_POWER = "POWER";
+    public static final String CMD_PLAYBACK = "PLAYBACK";
+    public static final String CMD_PAUSE = "PAUSE";
+    public static final String CMD_SKIPFWD = "SKIPFWD";
+    public static final String CMD_SKIPREV = "SKIPREV";
+    public static final String CMD_CUE = "CUE";
+    public static final String CMD_REV = "REV";
+
+    // Map the player status numbers to the corresponding i18n translation file keys
+    public static final Map<String, String> STATUS_MAP = Map.of(STOP_STATUS, "stop", OPEN_STATUS, "open", "02",
+            "reverse", "05", "forward", "06", "slowfwd", OFF_STATUS, "off", PLAY_STATUS, "play", PAUSE_STATUS, "pause",
+            "86", "slowrev");
+
+    // pre-define the POST body for status update calls
+    public static final Fields REVIEW_POST_CMD = new Fields();
+    static {
+        REVIEW_POST_CMD.add("cCMD_REVIEW.x", "100");
+        REVIEW_POST_CMD.add("cCMD_REVIEW.y", "100");
+    }
+
+    public static final Fields PST_POST_CMD = new Fields();
+    static {
+        PST_POST_CMD.add("cCMD_PST.x", "100");
+        PST_POST_CMD.add("cCMD_PST.y", "100");
+    }
+
+    public static final Fields GET_STATUS_POST_CMD = new Fields();
+    static {
+        GET_STATUS_POST_CMD.add("cCMD_GET_STATUS.x", "100");
+        GET_STATUS_POST_CMD.add("cCMD_GET_STATUS.y", "100");
+    }
+
+    public static final Fields GET_NONCE_CMD = new Fields();
+    static {
+        GET_NONCE_CMD.add("SID", "1234ABCD");
+    }
+}
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayConfiguration.java b/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayConfiguration.java
new file mode 100644 (file)
index 0000000..5f4e379
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2024 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.panasonicbdp.internal;
+
+import static org.openhab.binding.panasonicbdp.internal.PanaBlurayBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link PanaBlurayConfiguration} is the class used to match the
+ * thing configuration.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class PanaBlurayConfiguration {
+    public String hostName = EMPTY;
+    public int refresh = DEFAULT_REFRESH_PERIOD_SEC;
+    public String playerKey = EMPTY;
+}
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayHandlerFactory.java b/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/PanaBlurayHandlerFactory.java
new file mode 100644 (file)
index 0000000..ec17f03
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2024 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.panasonicbdp.internal;
+
+import static org.openhab.binding.panasonicbdp.internal.PanaBlurayBindingConstants.SUPPORTED_THING_TYPES_UIDS;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.panasonicbdp.internal.handler.PanaBlurayHandler;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.io.net.http.HttpClientFactory;
+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 PanaBlurayHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.panasonicbdp")
+public class PanaBlurayHandlerFactory extends BaseThingHandlerFactory {
+
+    private final HttpClient httpClient;
+    private final TranslationProvider translationProvider;
+    private final LocaleProvider localeProvider;
+
+    @Activate
+    public PanaBlurayHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
+            @Reference TranslationProvider translationProvider, @Reference LocaleProvider localeProvider) {
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+        this.translationProvider = translationProvider;
+        this.localeProvider = localeProvider;
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        if (SUPPORTED_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
+            return new PanaBlurayHandler(thing, httpClient, translationProvider, localeProvider);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/discovery/PanaBlurayDiscoveryParticipant.java b/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/discovery/PanaBlurayDiscoveryParticipant.java
new file mode 100644 (file)
index 0000000..4793383
--- /dev/null
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2010-2024 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.panasonicbdp.internal.discovery;
+
+import static org.openhab.binding.panasonicbdp.internal.PanaBlurayBindingConstants.*;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.jupnp.model.meta.RemoteDevice;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * Discovery Service for Panasonic Blu-ray Players.
+ *
+ * @author Michael Lobstein - Initial contribution
+ *
+ */
+@NonNullByDefault
+@Component(immediate = true)
+public class PanaBlurayDiscoveryParticipant implements UpnpDiscoveryParticipant {
+
+    private final Logger logger = LoggerFactory.getLogger(PanaBlurayDiscoveryParticipant.class);
+
+    private static final String MANUFACTURER = "Panasonic";
+    private static final String UB_PREFIX = "UB";
+    private static final String UPNP_RESULT_MEDIA_RENDERER = "MediaRenderer";
+
+    private static final List<String> MODELS = List.of("BDT110", "BDT210", "BDT310", "BDT120", "BDT220", "BDT320",
+            "BBT01", "BDT500", "UB420", "UB424", "UB820", "UB824", "UB9000", "UB9004");
+
+    @Override
+    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
+        return SUPPORTED_THING_TYPES_UIDS;
+    }
+
+    @Override
+    public @Nullable DiscoveryResult createResult(RemoteDevice device) {
+        final ThingUID uid = getThingUID(device);
+        if (uid != null) {
+            final Map<String, Object> properties = new HashMap<>(2);
+
+            final URL url = device.getIdentity().getDescriptorURL();
+            final String label = device.getDetails().getFriendlyName();
+
+            properties.put(PROPERTY_UUID, uid.getId());
+            properties.put(PROPERTY_HOST_NAME, url.getHost());
+
+            final DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
+                    .withRepresentationProperty(PROPERTY_UUID).withLabel(label).build();
+
+            logger.debug("Created a DiscoveryResult for device '{}' with UID '{}'", label, uid.getId());
+            return result;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public @Nullable ThingUID getThingUID(RemoteDevice device) {
+        if (device.getDetails().getManufacturerDetails().getManufacturer() != null
+                && device.getDetails().getModelDetails().getModelNumber() != null) {
+            if (device.getDetails().getManufacturerDetails().getManufacturer().startsWith(MANUFACTURER)) {
+                logger.debug("Panasonic UPNP device found at {}", device.getIdentity().getDescriptorURL().getHost());
+                String id = device.getIdentity().getUdn().getIdentifierString().replaceAll(":", EMPTY);
+
+                // Shorten to just the mac address, ie: 4D454930-0600-1000-8000-80C755A1D630 -> 80C755A1D630
+                if (id.length() > 12) {
+                    id = id.substring(id.length() - 12);
+                }
+
+                final String modelNumber = device.getDetails().getModelDetails().getModelNumber();
+
+                if (MODELS.stream().anyMatch(supportedModel -> (modelNumber.contains(supportedModel)))) {
+                    if (modelNumber.contains(UB_PREFIX)) {
+                        // UHD (UB-nnnn) players return multiple UPNP results, ignore all but the 'MediaRenderer' result
+                        if (UPNP_RESULT_MEDIA_RENDERER.equals(device.getType().getType())) {
+                            return new ThingUID(THING_TYPE_UHD_PLAYER, id);
+                        }
+                    } else {
+                        return new ThingUID(THING_TYPE_BD_PLAYER, id);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/handler/PanaBlurayHandler.java b/bundles/org.openhab.binding.panasonicbdp/src/main/java/org/openhab/binding/panasonicbdp/internal/handler/PanaBlurayHandler.java
new file mode 100644 (file)
index 0000000..9fbbeaf
--- /dev/null
@@ -0,0 +1,391 @@
+/**
+ * Copyright (c) 2010-2024 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.panasonicbdp.internal.handler;
+
+import static org.eclipse.jetty.http.HttpStatus.OK_200;
+import static org.openhab.binding.panasonicbdp.internal.PanaBlurayBindingConstants.*;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.FormContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.Fields;
+import org.openhab.binding.panasonicbdp.internal.PanaBlurayConfiguration;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.NextPreviousType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PlayPauseType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RewindFastforwardType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Channel;
+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.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PanaBlurayHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public class PanaBlurayHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(PanaBlurayHandler.class);
+    private final HttpClient httpClient;
+    private static final int REQUEST_TIMEOUT = 5000;
+
+    private @Nullable ScheduledFuture<?> refreshJob;
+
+    private String urlStr = "http://%host%/WAN/dvdr/dvdr_ctrl.cgi";
+    private String nonceUrlStr = "http://%host%/cgi-bin/get_nonce.cgi";
+    private int refreshInterval = DEFAULT_REFRESH_PERIOD_SEC;
+    private String playerMode = EMPTY;
+    private String playerKey = EMPTY;
+    private boolean debounce = true;
+    private boolean authEnabled = false;
+    private Object sequenceLock = new Object();
+    private ThingTypeUID thingTypeUID = THING_TYPE_BD_PLAYER;
+
+    private final TranslationProvider translationProvider;
+    private final LocaleProvider localeProvider;
+    private final @Nullable Bundle bundle;
+
+    public PanaBlurayHandler(Thing thing, HttpClient httpClient, @Reference TranslationProvider translationProvider,
+            @Reference LocaleProvider localeProvider) {
+        super(thing);
+        this.httpClient = httpClient;
+        this.translationProvider = translationProvider;
+        this.localeProvider = localeProvider;
+        this.bundle = FrameworkUtil.getBundle(PanaBlurayHandler.class);
+    }
+
+    @Override
+    public void initialize() {
+        logger.debug("Initializing Panasonic Blu-ray Player handler.");
+        PanaBlurayConfiguration config = getConfigAs(PanaBlurayConfiguration.class);
+
+        this.thingTypeUID = thing.getThingTypeUID();
+
+        final String host = config.hostName;
+        final String playerKey = config.playerKey;
+
+        if (!host.isBlank()) {
+            urlStr = urlStr.replace("%host%", host);
+            nonceUrlStr = nonceUrlStr.replace("%host%", host);
+        } else {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.hostname");
+            return;
+        }
+
+        if (!playerKey.isBlank()) {
+            if (playerKey.length() != 32) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.keyerror");
+                return;
+            }
+            this.playerKey = playerKey;
+            authEnabled = true;
+        }
+        refreshInterval = config.refresh;
+
+        updateStatus(ThingStatus.UNKNOWN);
+        startAutomaticRefresh();
+    }
+
+    /**
+     * Start the job to periodically get a status update from the player
+     */
+    private void startAutomaticRefresh() {
+        ScheduledFuture<?> refreshJob = this.refreshJob;
+        if (refreshJob == null || refreshJob.isCancelled()) {
+            this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshPlayerStatus, 0, refreshInterval,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Sends commands to the player to get status information and updates the channels
+     */
+    private void refreshPlayerStatus() {
+        final String[] playerStatusRespArr = sendCommand(REVIEW_POST_CMD, urlStr).split(CRLF);
+
+        if (playerStatusRespArr.length == 1 && playerStatusRespArr[0].isBlank()) {
+            return;
+        } else if (playerStatusRespArr.length >= 2) {
+            // CMD_REVIEW response second line, 4th group is the status:
+            // F,,,07,00,,8,,,,1,000:00,,05:10,F,FF:FF,0000,0000,0000,0000
+            final String playerStatusArr[] = playerStatusRespArr[1].split(COMMA);
+
+            if (playerStatusArr.length >= 4) {
+                if (getThing().getStatus() != ThingStatus.ONLINE) {
+                    updateStatus(ThingStatus.ONLINE);
+                }
+
+                // update playerMode if different
+                if (!playerMode.equals(playerStatusArr[3])) {
+                    playerMode = playerStatusArr[3];
+                    final String i18nKey = STATUS_MAP.get(playerMode) != null ? STATUS_MAP.get(playerMode) : "unknown";
+                    updateState(PLAYER_STATUS, new StringType(translationProvider.getText(bundle, "status." + i18nKey,
+                            i18nKey, localeProvider.getLocale())));
+                    updateState(CONTROL, PLAY_STATUS.equals(playerMode) ? PlayPauseType.PLAY : PlayPauseType.PAUSE);
+
+                    // playback stopped, tray open, or power switched off, zero out time and chapters
+                    if (!isPlayingMode()) {
+                        updateState(TIME_ELAPSED, UnDefType.UNDEF);
+                        updateState(TIME_TOTAL, UnDefType.UNDEF);
+                        updateState(CHAPTER_CURRENT, UnDefType.UNDEF);
+                        updateState(CHAPTER_TOTAL, UnDefType.UNDEF);
+                    }
+                }
+
+                if (debounce) {
+                    updateState(POWER, OnOffType.from(!OFF_STATUS.equals(playerMode)));
+                }
+                debounce = true;
+            } else {
+                updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.polling");
+                return;
+            }
+        } else {
+            updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.polling");
+            return;
+        }
+
+        // if time/chapter channels are not linked or player is stopped or paused there is no need to make extra calls
+        if (isAnyStatusChannelsLinked() && isPlayingMode() && !PAUSE_STATUS.equals(playerMode)) {
+            final String[] pstRespArr = sendCommand(PST_POST_CMD, urlStr).split(CRLF);
+
+            if (pstRespArr.length >= 2) {
+                // CMD_PST response second line: 1,1543,0,00000000 (play mode, current time, ?, ?)
+                final String pstArr[] = pstRespArr[1].split(COMMA);
+
+                if (pstArr.length >= 2) {
+                    try {
+                        updateState(TIME_ELAPSED, new QuantityType<>(Integer.parseInt(pstArr[1]), API_SECONDS_UNIT));
+                    } catch (NumberFormatException nfe) {
+                        logger.debug("Error parsing time elapsed integer in CMD_PST message: {}", pstRespArr[1]);
+                    }
+                }
+            }
+
+            // only BD players support the CMD_GET_STATUS command, it returns a 404 error on UHD models
+            if (THING_TYPE_BD_PLAYER.equals(thingTypeUID)
+                    && (isLinked(TIME_TOTAL) || isLinked(CHAPTER_CURRENT) || isLinked(CHAPTER_TOTAL))) {
+                final String[] getStatusRespArr = sendCommand(GET_STATUS_POST_CMD, urlStr).split(CRLF);
+
+                if (getStatusRespArr.length >= 2) {
+                    // CMD_GET_STATUS response second line: 1,0,0,1,5999,61440,500,1,16,00000000
+                    // (?, ?, ?, cur time, total time, title#?, ?, chapter #, total chapter, ?)
+                    final String getStatusArr[] = getStatusRespArr[1].split(COMMA);
+
+                    if (getStatusArr.length >= 10) {
+                        try {
+                            updateState(TIME_TOTAL,
+                                    new QuantityType<>(Integer.parseInt(getStatusArr[4]), API_SECONDS_UNIT));
+                            updateState(CHAPTER_CURRENT, new DecimalType(Integer.parseInt(getStatusArr[7])));
+                            updateState(CHAPTER_TOTAL, new DecimalType(Integer.parseInt(getStatusArr[8])));
+                        } catch (NumberFormatException nfe) {
+                            logger.debug("Error parsing integer in CMD_GET_STATUS message: {}", getStatusRespArr[1]);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> refreshJob = this.refreshJob;
+        if (refreshJob != null) {
+            refreshJob.cancel(true);
+            this.refreshJob = null;
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        synchronized (sequenceLock) {
+            if (command instanceof RefreshType) {
+                logger.debug("Unsupported refresh command: {}", command);
+            } else if (BUTTON.equals(channelUID.getId()) || POWER.equals(channelUID.getId())
+                    || CONTROL.equals(channelUID.getId())) {
+                final String commandStr;
+                if (command instanceof OnOffType) {
+                    commandStr = CMD_POWER + command; // e.g. POWERON or POWEROFF
+                    // if the power is switched on while the polling is running, the switch could bounce back to off,
+                    // set this flag to stop the first polling event from changing the state of the switch to give the
+                    // player time to start up and report the correct power status on the next poll
+                    debounce = false;
+                } else if (command instanceof PlayPauseType || command instanceof NextPreviousType
+                        || command instanceof RewindFastforwardType) {
+                    if (command == PlayPauseType.PLAY) {
+                        commandStr = CMD_PLAYBACK;
+                    } else if (command == PlayPauseType.PAUSE) {
+                        commandStr = CMD_PAUSE;
+                    } else if (command == NextPreviousType.NEXT) {
+                        commandStr = CMD_SKIPFWD;
+                    } else if (command == NextPreviousType.PREVIOUS) {
+                        commandStr = CMD_SKIPREV;
+                    } else if (command == RewindFastforwardType.FASTFORWARD) {
+                        commandStr = CMD_CUE;
+                    } else if (command == RewindFastforwardType.REWIND) {
+                        commandStr = CMD_REV;
+                    } else {
+                        logger.debug("Invalid control command: {}", command);
+                        return;
+                    }
+                } else {
+                    commandStr = command.toString();
+                }
+
+                // build the fields to POST the RC_ command string
+                Fields fields = new Fields();
+                fields.add("cCMD_RC_" + commandStr + ".x", "100");
+                fields.add("cCMD_RC_" + commandStr + ".y", "100");
+
+                // if command authentication enabled, get nonce to create authKey and add it to the POST fields
+                if (authEnabled) {
+                    final String nonce = sendCommand(GET_NONCE_CMD, nonceUrlStr).trim();
+                    if (nonce.isBlank()) {
+                        return;
+                    } else if (nonce.length() != 32) {
+                        logger.debug("Error retrieving nonce, message was: {}", nonce);
+                        updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.nonce");
+                        return;
+                    }
+                    try {
+                        fields.add("cAUTH_FORM", playerKey.substring(0, playerKey.contains("2") ? 2 : 3));
+                        fields.add("cAUTH_VALUE", getAuthKey(playerKey + nonce));
+                    } catch (NoSuchAlgorithmException e) {
+                        logger.debug("Error creating auth key: {}", e.getMessage());
+                        updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.authkey");
+                        return;
+                    }
+                }
+
+                // send the command to the player
+                sendCommand(fields, urlStr);
+            } else {
+                logger.debug("Unsupported command: {}", command);
+            }
+        }
+    }
+
+    /**
+     * Sends a command to the player using a pre-built post body
+     *
+     * @param fields a pre-built post body to send to the player
+     * @param url the url to receive the command
+     * @return the response string from the player
+     */
+    private String sendCommand(Fields fields, String url) {
+        try {
+            logger.trace("POST url: {}, data: {}", url, fields);
+            ContentResponse response = httpClient.POST(url).agent(USER_AGENT).method(HttpMethod.POST)
+                    .content(new FormContentProvider(fields)).timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).send();
+
+            final String output = response.getContentAsString();
+            logger.trace("Response status: {}, response: {}", response.getStatus(), output);
+
+            if (response.getStatus() != OK_200) {
+                updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.polling");
+                return EMPTY;
+            } else if (output.startsWith(PLAYER_CMD_ERR)) {
+                updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "@text/error.invalid");
+                return EMPTY;
+            }
+
+            return output;
+        } catch (TimeoutException | ExecutionException e) {
+            logger.debug("Error executing command: {}, {}", fields.getNames().iterator().next(), e.getMessage());
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "@text/error.exception");
+        } catch (InterruptedException e) {
+            logger.debug("InterruptedException executing command: {}, {}", fields.getNames().iterator().next(),
+                    e.getMessage());
+            Thread.currentThread().interrupt();
+        }
+        return EMPTY;
+    }
+
+    /*
+     * Returns true if the player is currently in a playing mode.
+     */
+    private boolean isPlayingMode() {
+        return !(STOP_STATUS.equals(playerMode) || OPEN_STATUS.equals(playerMode) || OFF_STATUS.equals(playerMode));
+    }
+
+    /*
+     * Returns true if any of the time or chapter channels are linked depending on which thing type is used.
+     */
+    private boolean isAnyStatusChannelsLinked() {
+        if (THING_TYPE_BD_PLAYER.equals(thingTypeUID)) {
+            return isLinked(TIME_ELAPSED) || isLinked(TIME_TOTAL) || isLinked(CHAPTER_CURRENT)
+                    || isLinked(CHAPTER_TOTAL);
+        }
+        return isLinked(TIME_ELAPSED);
+    }
+
+    @Override
+    public boolean isLinked(String channelName) {
+        final Channel channel = this.thing.getChannel(channelName);
+        return channel != null ? isLinked(channel.getUID()) : false;
+    }
+
+    /**
+     * Returns a SHA-256 hash of the input string
+     *
+     * @param input the input string to generate the hash from
+     * @return the 256 bit hash string
+     */
+    private String getAuthKey(String input) throws NoSuchAlgorithmException {
+        final MessageDigest md = MessageDigest.getInstance(SHA_256_ALGORITHM);
+        final byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
+
+        // convert byte array into signum representation
+        final BigInteger number = new BigInteger(1, hash);
+
+        // convert message digest into hex value
+        final StringBuilder hexString = new StringBuilder(number.toString(16));
+
+        // pad with leading zeros
+        while (hexString.length() < 32) {
+            hexString.insert(0, "0");
+        }
+
+        return hexString.toString().toUpperCase();
+    }
+}
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..a9a2171
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="panasonicbdp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+
+       <type>binding</type>
+       <name>Panasonic Blu-ray Player Binding</name>
+       <description>Controls Panasonic 2011/2012 Blu-ray and 2018 UHD Blu-ray Players</description>
+       <connection>local</connection>
+
+       <discovery-methods>
+               <discovery-method>
+                       <service-type>upnp</service-type>
+                       <match-properties>
+                               <match-property>
+                                       <name>manufacturer</name>
+                                       <regex>(?i)Panasonic</regex>
+                               </match-property>
+                               <match-property>
+                                       <name>modelName</name>
+                                       <regex>(?i)BDT[1-3][1-2]0|BBT01|BDT500|UB[4|8]2[0|4]|UB900[0|4]</regex>
+                               </match-property>
+                       </match-properties>
+               </discovery-method>
+       </discovery-methods>
+
+</addon:addon>
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/i18n/panasonicbdp.properties b/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/i18n/panasonicbdp.properties
new file mode 100644 (file)
index 0000000..7d61871
--- /dev/null
@@ -0,0 +1,175 @@
+# add-on
+
+addon.panasonicbdp.name = Panasonic Blu-ray Player Binding
+addon.panasonicbdp.description = Controls Panasonic 2011/2012 Blu-ray and 2018 UHD Blu-ray Players
+
+# thing types
+
+thing-type.panasonicbdp.bd-player.label = Panasonic Blu-ray Player
+thing-type.panasonicbdp.bd-player.description = Panasonic Blu-ray Player model DMP-BDT[1-3]10, DMP-BDT[1-3]20, DMP-BBT01 or DMP-BDT500
+thing-type.panasonicbdp.bd-player.channel.power.label = Power
+thing-type.panasonicbdp.bd-player.channel.power.description = Turn the power for the player on or off
+thing-type.panasonicbdp.uhd-player.label = Panasonic UHD Blu-ray Player
+thing-type.panasonicbdp.uhd-player.description = Panasonic UHD Blu-ray model DP-UB420/424, DP-UB820/824 or DP-UB9000/9004
+thing-type.panasonicbdp.uhd-player.channel.power.label = Power
+thing-type.panasonicbdp.uhd-player.channel.power.description = Turn the power for the player on or off
+
+# thing types config
+
+thing-type.config.panasonicbdp.bd-player.hostName.label = Player Host Name/IP Address
+thing-type.config.panasonicbdp.bd-player.hostName.description = Host Name or IP Address of the player
+thing-type.config.panasonicbdp.bd-player.refresh.label = Refresh Interval
+thing-type.config.panasonicbdp.bd-player.refresh.description = Specifies the refresh interval in seconds
+thing-type.config.panasonicbdp.uhd-player.hostName.label = Player Host Name/IP Address
+thing-type.config.panasonicbdp.uhd-player.hostName.description = Host Name or IP Address of the player
+thing-type.config.panasonicbdp.uhd-player.playerKey.label = Player Key
+thing-type.config.panasonicbdp.uhd-player.playerKey.description = To enable authentication of control commands, a key for the player must be specified
+thing-type.config.panasonicbdp.uhd-player.refresh.label = Refresh Interval
+thing-type.config.panasonicbdp.uhd-player.refresh.description = Specifies the refresh interval in seconds
+
+# channel types
+
+channel-type.panasonicbdp.button.label = Remote Button
+channel-type.panasonicbdp.button.description = A remote button press to send to the player
+channel-type.panasonicbdp.button.state.option.POWERON = Power On
+channel-type.panasonicbdp.button.state.option.POWEROFF = Power Off
+channel-type.panasonicbdp.button.state.option.POWER = Power Toggle
+channel-type.panasonicbdp.button.state.option.PLAYBACK = Play
+channel-type.panasonicbdp.button.state.option.PAUSE = Pause
+channel-type.panasonicbdp.button.state.option.STOP = Stop
+channel-type.panasonicbdp.button.state.option.CUE = Fast Forward
+channel-type.panasonicbdp.button.state.option.REV = Reverse
+channel-type.panasonicbdp.button.state.option.SKIPFWD = Skip Forward
+channel-type.panasonicbdp.button.state.option.SKIPREV = Skip Back
+channel-type.panasonicbdp.button.state.option.OP_CL = Open/Close
+channel-type.panasonicbdp.button.state.option.DSPSEL = Status
+channel-type.panasonicbdp.button.state.option.TITLE = Top Menu
+channel-type.panasonicbdp.button.state.option.PUPMENU = Pop-up Menu
+channel-type.panasonicbdp.button.state.option.UP = Up
+channel-type.panasonicbdp.button.state.option.DOWN = Down
+channel-type.panasonicbdp.button.state.option.LEFT = Left
+channel-type.panasonicbdp.button.state.option.RIGHT = Right
+channel-type.panasonicbdp.button.state.option.SELECT = OK
+channel-type.panasonicbdp.button.state.option.MENU = Submenu
+channel-type.panasonicbdp.button.state.option.RETURN = Return
+channel-type.panasonicbdp.button.state.option.D1 = 1
+channel-type.panasonicbdp.button.state.option.D2 = 2
+channel-type.panasonicbdp.button.state.option.D3 = 3
+channel-type.panasonicbdp.button.state.option.D4 = 4
+channel-type.panasonicbdp.button.state.option.D5 = 5
+channel-type.panasonicbdp.button.state.option.D6 = 6
+channel-type.panasonicbdp.button.state.option.D7 = 7
+channel-type.panasonicbdp.button.state.option.D8 = 8
+channel-type.panasonicbdp.button.state.option.D9 = 9
+channel-type.panasonicbdp.button.state.option.D0 = 0
+channel-type.panasonicbdp.button.state.option.D12 = 12
+channel-type.panasonicbdp.button.state.option.CLEAR = Cancel
+channel-type.panasonicbdp.button.state.option.SHARP = #
+channel-type.panasonicbdp.button.state.option.RED = Red
+channel-type.panasonicbdp.button.state.option.GREEN = Green
+channel-type.panasonicbdp.button.state.option.BLUE = Blue
+channel-type.panasonicbdp.button.state.option.YELLOW = Yellow
+channel-type.panasonicbdp.button.state.option.MLTNAVI = Home
+channel-type.panasonicbdp.button.state.option.V_CAST = Viera Cast
+channel-type.panasonicbdp.button.state.option.NETWORK = Network
+channel-type.panasonicbdp.button.state.option.SETUP = Setup
+channel-type.panasonicbdp.button.state.option.EXIT = Exit
+channel-type.panasonicbdp.button.state.option.AUDIOSEL = Audio
+channel-type.panasonicbdp.button.state.option.3D = 3D
+channel-type.panasonicbdp.button.state.option.P_IN_P = PIP
+channel-type.panasonicbdp.button.state.option.OSDONOFF = OSD
+channel-type.panasonicbdp.button.state.option.SHFWD2 = (swipe in CW circle)
+channel-type.panasonicbdp.button.state.option.SHREV2 = (swipe in CCW circle)
+channel-type.panasonicbdp.chapter-current.label = Current Chapter
+channel-type.panasonicbdp.chapter-current.description = The current chapter number
+channel-type.panasonicbdp.chapter-total.label = Total Chapters
+channel-type.panasonicbdp.chapter-total.description = The total number of chapters in the current title
+channel-type.panasonicbdp.control.label = Control
+channel-type.panasonicbdp.control.description = Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind
+channel-type.panasonicbdp.player-status.label = Player Status
+channel-type.panasonicbdp.player-status.description = The current player status
+channel-type.panasonicbdp.time-elapsed.label = Playback Time
+channel-type.panasonicbdp.time-elapsed.description = The current playback time elapsed
+channel-type.panasonicbdp.time-total.label = Total Time
+channel-type.panasonicbdp.time-total.description = The total length of the current title
+channel-type.panasonicbdp.uhd-button.label = Remote Button
+channel-type.panasonicbdp.uhd-button.description = A remote button press to send to the player
+channel-type.panasonicbdp.uhd-button.state.option.POWERON = Power On
+channel-type.panasonicbdp.uhd-button.state.option.POWEROFF = Power Off
+channel-type.panasonicbdp.uhd-button.state.option.POWER = Power Toggle
+channel-type.panasonicbdp.uhd-button.state.option.PLAYBACK = Play
+channel-type.panasonicbdp.uhd-button.state.option.PAUSE = Pause
+channel-type.panasonicbdp.uhd-button.state.option.STOP = Stop
+channel-type.panasonicbdp.uhd-button.state.option.CUE = Fast Forward
+channel-type.panasonicbdp.uhd-button.state.option.REV = Reverse
+channel-type.panasonicbdp.uhd-button.state.option.SKIPFWD = Skip Forward
+channel-type.panasonicbdp.uhd-button.state.option.SKIPREV = Skip Back
+channel-type.panasonicbdp.uhd-button.state.option.MNSKIP = Manual Skip +60s
+channel-type.panasonicbdp.uhd-button.state.option.MNBACK = Manual Skip -10s
+channel-type.panasonicbdp.uhd-button.state.option.OP_CL = Open/Close
+channel-type.panasonicbdp.uhd-button.state.option.DSPSEL = Status
+channel-type.panasonicbdp.uhd-button.state.option.TITLE = Top Menu
+channel-type.panasonicbdp.uhd-button.state.option.PUPMENU = Pop-up Menu
+channel-type.panasonicbdp.uhd-button.state.option.UP = Up
+channel-type.panasonicbdp.uhd-button.state.option.DOWN = Down
+channel-type.panasonicbdp.uhd-button.state.option.LEFT = Left
+channel-type.panasonicbdp.uhd-button.state.option.RIGHT = Right
+channel-type.panasonicbdp.uhd-button.state.option.SELECT = OK
+channel-type.panasonicbdp.uhd-button.state.option.MENU = Submenu
+channel-type.panasonicbdp.uhd-button.state.option.RETURN = Return
+channel-type.panasonicbdp.uhd-button.state.option.D1 = 1
+channel-type.panasonicbdp.uhd-button.state.option.D2 = 2
+channel-type.panasonicbdp.uhd-button.state.option.D3 = 3
+channel-type.panasonicbdp.uhd-button.state.option.D4 = 4
+channel-type.panasonicbdp.uhd-button.state.option.D5 = 5
+channel-type.panasonicbdp.uhd-button.state.option.D6 = 6
+channel-type.panasonicbdp.uhd-button.state.option.D7 = 7
+channel-type.panasonicbdp.uhd-button.state.option.D8 = 8
+channel-type.panasonicbdp.uhd-button.state.option.D9 = 9
+channel-type.panasonicbdp.uhd-button.state.option.D0 = 0
+channel-type.panasonicbdp.uhd-button.state.option.D12 = 12
+channel-type.panasonicbdp.uhd-button.state.option.CLEAR = Clear
+channel-type.panasonicbdp.uhd-button.state.option.SHARP = #
+channel-type.panasonicbdp.uhd-button.state.option.RED = Red
+channel-type.panasonicbdp.uhd-button.state.option.GREEN = Green
+channel-type.panasonicbdp.uhd-button.state.option.BLUE = Blue
+channel-type.panasonicbdp.uhd-button.state.option.YELLOW = Yellow
+channel-type.panasonicbdp.uhd-button.state.option.MLTNAVI = Home
+channel-type.panasonicbdp.uhd-button.state.option.NETFLIX = Netflix
+channel-type.panasonicbdp.uhd-button.state.option.V_CAST = Viera Cast
+channel-type.panasonicbdp.uhd-button.state.option.NETWORK = Network
+channel-type.panasonicbdp.uhd-button.state.option.SETUP = Setup
+channel-type.panasonicbdp.uhd-button.state.option.EXIT = Exit
+channel-type.panasonicbdp.uhd-button.state.option.AUDIOSEL = Audio
+channel-type.panasonicbdp.uhd-button.state.option.TITLEONOFF = Subtitle
+channel-type.panasonicbdp.uhd-button.state.option.CLOSED_CAPTION = Closed Caption
+channel-type.panasonicbdp.uhd-button.state.option.PLAYBACKINFO = Playback Info
+channel-type.panasonicbdp.uhd-button.state.option.HDR_PICTUREMODE = HDR Picture Mode
+channel-type.panasonicbdp.uhd-button.state.option.MIRACAST = Mirroring
+channel-type.panasonicbdp.uhd-button.state.option.PICTURESETTINGS = Picture Setting
+channel-type.panasonicbdp.uhd-button.state.option.SOUNDEFFECT = Sound Effect
+channel-type.panasonicbdp.uhd-button.state.option.HIGHCLARITY = High Clarity
+channel-type.panasonicbdp.uhd-button.state.option.SKIP_THE_TRAILER = Skip The Trailer
+
+# player status descriptions
+
+status.off = Power Off
+status.open = Tray Open
+status.stop = Stopped
+status.play = Playback
+status.pause = Pause Playback
+status.reverse = Rev Playback
+status.forward = Cue Playback
+status.slowfwd = Slow Forward Playback
+status.slowrev = Slow Backward Playback
+status.unknown = Unknown
+
+# error status descriptions
+
+error.hostname = Host Name must be specified.
+error.exception = Error communicating with the player.
+error.polling = Invalid status response received from player.
+error.keyerror = Error sending command to the player. Invalid playerKey length, should be 32 characters.
+error.nonce = Error sending command to the player. Nonce retrieval failure.
+error.authkey = Error sending command to the player. Error creating auth key.
+error.invalid = Error sending command to the player. See README for player configuration instructions.
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/bdplayer.xml b/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/bdplayer.xml
new file mode 100644 (file)
index 0000000..2e028f0
--- /dev/null
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="panasonicbdp"
+       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">
+
+       <!-- Panasonic Blu-ray Player Thing -->
+       <thing-type id="bd-player">
+               <label>Panasonic Blu-ray Player</label>
+               <description>
+                       Panasonic Blu-ray Player model DMP-BDT[1-3]10, DMP-BDT[1-3]20, DMP-BBT01 or DMP-BDT500
+               </description>
+
+               <channels>
+                       <channel id="power" typeId="system.power">
+                               <label>Power</label>
+                               <description>Turn the power for the player on or off</description>
+                       </channel>
+                       <channel id="button" typeId="button"/>
+                       <channel id="control" typeId="control"/>
+                       <channel id="player-status" typeId="player-status"/>
+                       <channel id="time-elapsed" typeId="time-elapsed"/>
+                       <channel id="time-total" typeId="time-total"/>
+                       <channel id="chapter-current" typeId="chapter-current"/>
+                       <channel id="chapter-total" typeId="chapter-total"/>
+               </channels>
+
+               <representation-property>uuid</representation-property>
+
+               <config-description>
+                       <parameter name="hostName" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Player Host Name/IP Address</label>
+                               <description>Host Name or IP Address of the player</description>
+                       </parameter>
+                       <parameter name="refresh" type="integer" min="5" required="false" unit="s">
+                               <label>Refresh Interval</label>
+                               <description>Specifies the refresh interval in seconds</description>
+                               <default>5</default>
+                               <unitLabel>s</unitLabel>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-type id="button">
+               <item-type>String</item-type>
+               <label>Remote Button</label>
+               <description>A remote button press to send to the player</description>
+               <state>
+                       <options>
+                               <option value="POWERON">Power On</option>
+                               <option value="POWEROFF">Power Off</option>
+                               <option value="POWER">Power Toggle</option>
+                               <option value="PLAYBACK">Play</option>
+                               <option value="PAUSE">Pause</option>
+                               <option value="STOP">Stop</option>
+                               <option value="CUE">Fast Forward</option>
+                               <option value="REV">Reverse</option>
+                               <option value="SKIPFWD">Skip Forward</option>
+                               <option value="SKIPREV">Skip Back</option>
+                               <option value="OP_CL">Open/Close</option>
+                               <option value="DSPSEL">Status</option>
+                               <option value="TITLE">Top Menu</option>
+                               <option value="PUPMENU">Pop-up Menu</option>
+                               <option value="UP">Up</option>
+                               <option value="DOWN">Down</option>
+                               <option value="LEFT">Left</option>
+                               <option value="RIGHT">Right</option>
+                               <option value="SELECT">OK</option>
+                               <option value="MENU">Submenu</option>
+                               <option value="RETURN">Return</option>
+                               <option value="D1">1</option>
+                               <option value="D2">2</option>
+                               <option value="D3">3</option>
+                               <option value="D4">4</option>
+                               <option value="D5">5</option>
+                               <option value="D6">6</option>
+                               <option value="D7">7</option>
+                               <option value="D8">8</option>
+                               <option value="D9">9</option>
+                               <option value="D0">0</option>
+                               <option value="D12">12</option>
+                               <option value="CLEAR">Cancel</option>
+                               <option value="SHARP">#</option>
+                               <option value="RED">Red</option>
+                               <option value="GREEN">Green</option>
+                               <option value="BLUE">Blue</option>
+                               <option value="YELLOW">Yellow</option>
+                               <option value="MLTNAVI">Home</option>
+                               <option value="V_CAST">Viera Cast</option>
+                               <option value="NETWORK">Network</option>
+                               <option value="SETUP">Setup</option>
+                               <option value="EXIT">Exit</option>
+                               <option value="AUDIOSEL">Audio</option>
+                               <option value="3D">3D</option>
+                               <option value="P_IN_P">PIP</option>
+                               <option value="OSDONOFF">OSD</option>
+                               <option value="SHFWD2">(swipe in CW circle)</option>
+                               <option value="SHREV2">(swipe in CCW circle)</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="control">
+               <item-type>Player</item-type>
+               <label>Control</label>
+               <description>Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind</description>
+               <category>Player</category>
+       </channel-type>
+
+       <channel-type id="player-status">
+               <item-type>String</item-type>
+               <label>Player Status</label>
+               <description>The current player status</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="time-elapsed">
+               <item-type>Number:Time</item-type>
+               <label>Playback Time</label>
+               <description>The current playback time elapsed</description>
+               <category>Time</category>
+               <state readOnly="true" pattern="%d %unit%"/>
+       </channel-type>
+
+       <channel-type id="time-total">
+               <item-type>Number:Time</item-type>
+               <label>Total Time</label>
+               <description>The total length of the current title</description>
+               <category>Time</category>
+               <state readOnly="true" pattern="%d %unit%"/>
+       </channel-type>
+
+       <channel-type id="chapter-current">
+               <item-type>Number</item-type>
+               <label>Current Chapter</label>
+               <description>The current chapter number</description>
+               <state readOnly="true" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="chapter-total">
+               <item-type>Number</item-type>
+               <label>Total Chapters</label>
+               <description>The total number of chapters in the current title</description>
+               <state readOnly="true" pattern="%d"/>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/uhdplayer.xml b/bundles/org.openhab.binding.panasonicbdp/src/main/resources/OH-INF/thing/uhdplayer.xml
new file mode 100644 (file)
index 0000000..928df16
--- /dev/null
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="panasonicbdp"
+       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">
+
+       <!-- Panasonic UHD Blu-ray Player Thing -->
+       <thing-type id="uhd-player">
+               <label>Panasonic UHD Blu-ray Player</label>
+               <description>
+                       Panasonic UHD Blu-ray model DP-UB420/424, DP-UB820/824 or DP-UB9000/9004
+               </description>
+
+               <channels>
+                       <channel id="power" typeId="system.power">
+                               <label>Power</label>
+                               <description>Turn the power for the player on or off</description>
+                       </channel>
+                       <channel id="button" typeId="uhd-button"/>
+                       <channel id="control" typeId="control"/>
+                       <channel id="player-status" typeId="player-status"/>
+                       <channel id="time-elapsed" typeId="time-elapsed"/>
+               </channels>
+
+               <representation-property>uuid</representation-property>
+
+               <config-description>
+                       <parameter name="hostName" type="text" required="true">
+                               <context>network-address</context>
+                               <label>Player Host Name/IP Address</label>
+                               <description>Host Name or IP Address of the player</description>
+                       </parameter>
+                       <parameter name="refresh" type="integer" min="5" required="false" unit="s">
+                               <label>Refresh Interval</label>
+                               <description>Specifies the refresh interval in seconds</description>
+                               <default>5</default>
+                               <unitLabel>s</unitLabel>
+                       </parameter>
+                       <parameter name="playerKey" type="text" required="false">
+                               <label>Player Key</label>
+                               <description>To enable authentication of control commands, a key for the player must be specified</description>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-type id="uhd-button">
+               <item-type>String</item-type>
+               <label>Remote Button</label>
+               <description>A remote button press to send to the player</description>
+               <state>
+                       <options>
+                               <option value="POWERON">Power On</option>
+                               <option value="POWEROFF">Power Off</option>
+                               <option value="POWER">Power Toggle</option>
+                               <option value="PLAYBACK">Play</option>
+                               <option value="PAUSE">Pause</option>
+                               <option value="STOP">Stop</option>
+                               <option value="CUE">Fast Forward</option>
+                               <option value="REV">Reverse</option>
+                               <option value="SKIPFWD">Skip Forward</option>
+                               <option value="SKIPREV">Skip Back</option>
+                               <option value="MNSKIP">Manual Skip +60s</option>
+                               <option value="MNBACK">Manual Skip -10s</option>
+                               <option value="OP_CL">Open/Close</option>
+                               <option value="DSPSEL">Status</option>
+                               <option value="TITLE">Top Menu</option>
+                               <option value="PUPMENU">Pop-up Menu</option>
+                               <option value="UP">Up</option>
+                               <option value="DOWN">Down</option>
+                               <option value="LEFT">Left</option>
+                               <option value="RIGHT">Right</option>
+                               <option value="SELECT">OK</option>
+                               <option value="MENU">Submenu</option>
+                               <option value="RETURN">Return</option>
+                               <option value="D1">1</option>
+                               <option value="D2">2</option>
+                               <option value="D3">3</option>
+                               <option value="D4">4</option>
+                               <option value="D5">5</option>
+                               <option value="D6">6</option>
+                               <option value="D7">7</option>
+                               <option value="D8">8</option>
+                               <option value="D9">9</option>
+                               <option value="D0">0</option>
+                               <option value="D12">12</option>
+                               <option value="CLEAR">Clear</option>
+                               <option value="SHARP">#</option>
+                               <option value="RED">Red</option>
+                               <option value="GREEN">Green</option>
+                               <option value="BLUE">Blue</option>
+                               <option value="YELLOW">Yellow</option>
+                               <option value="MLTNAVI">Home</option>
+                               <option value="NETFLIX">Netflix</option>
+                               <option value="V_CAST">Viera Cast</option>
+                               <option value="NETWORK">Network</option>
+                               <option value="SETUP">Setup</option>
+                               <option value="EXIT">Exit</option>
+                               <option value="AUDIOSEL">Audio</option>
+                               <option value="TITLEONOFF">Subtitle</option>
+                               <option value="CLOSED_CAPTION">Closed Caption</option>
+                               <option value="PLAYBACKINFO">Playback Info</option>
+                               <option value="HDR_PICTUREMODE">HDR Picture Mode</option>
+                               <option value="MIRACAST">Mirroring</option>
+                               <option value="PICTURESETTINGS">Picture Setting</option>
+                               <option value="SOUNDEFFECT">Sound Effect</option>
+                               <option value="HIGHCLARITY">High Clarity</option>
+                               <option value="SKIP_THE_TRAILER">Skip The Trailer</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="control">
+               <item-type>Player</item-type>
+               <label>Control</label>
+               <description>Transport Controls e.g. Play/Pause/Next/Previous/FForward/Rewind</description>
+               <category>Player</category>
+       </channel-type>
+
+       <channel-type id="player-status">
+               <item-type>String</item-type>
+               <label>Player Status</label>
+               <description>The current player status</description>
+               <state readOnly="true"/>
+       </channel-type>
+
+       <channel-type id="time-elapsed">
+               <item-type>Number:Time</item-type>
+               <label>Playback Time</label>
+               <description>The current playback time elapsed</description>
+               <category>Time</category>
+               <state readOnly="true" pattern="%d %unit%"/>
+       </channel-type>
+
+</thing:thing-descriptions>
index 7ded2a83bd725bba76e4196172d15027f4d8e082..14f345d1dfb22a16701b648511c7358aed6b79c1 100644 (file)
     <module>org.openhab.binding.oppo</module>
     <module>org.openhab.binding.orbitbhyve</module>
     <module>org.openhab.binding.orvibo</module>
+    <module>org.openhab.binding.panasonicbdp</module>
     <module>org.openhab.binding.paradoxalarm</module>
     <module>org.openhab.binding.pentair</module>
     <module>org.openhab.binding.phc</module>