/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
<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>
--- /dev/null
+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
--- /dev/null
+# 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 |
+| * (Cancel) | CLEAR |
+| # ([_]) | 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 |
+| * (Cancel) | CLEAR |
+| # ([_]) | 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 |
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+# 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.
--- /dev/null
+<?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>
--- /dev/null
+<?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>
<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>