From d8d5b3da629efb05c90b02d6a8378635c7efda81 Mon Sep 17 00:00:00 2001 From: mlobstein Date: Fri, 6 Sep 2024 09:43:41 -0500 Subject: [PATCH] [kaleidescape] Support discovery of Strato V (#17371) * Support discovery of Strato V Signed-off-by: Michael Lobstein Signed-off-by: Ciprian Pascu --- .../README.md | 6 +- .../src/main/feature/feature.xml | 1 + .../KaleidescapeBindingConstants.java | 4 + .../KaleidescapeStatusCodes.java | 102 ++++++++++------- .../KaleidescapeDiscoveryParticipant.java | 106 ++++++++++++++++++ .../KaleidescapeDiscoveryService.java | 66 +++-------- 6 files changed, 190 insertions(+), 95 deletions(-) create mode 100644 bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryParticipant.java diff --git a/bundles/org.openhab.binding.kaleidescape/README.md b/bundles/org.openhab.binding.kaleidescape/README.md index 906a767f6bd..71c04ca5393 100644 --- a/bundles/org.openhab.binding.kaleidescape/README.md +++ b/bundles/org.openhab.binding.kaleidescape/README.md @@ -16,13 +16,14 @@ The supported thing types are: `player` Any KPlayer, M Class [M300, M500, M700] or Cinema One 1st Gen player `cinemaone` Cinema One (2nd Gen) `alto` -`strato` Includes Strato, Strato S, or Strato C +`strato` Includes Strato, Strato S, Strato C or Strato V The binding supports either a TCP/IP connection or direct serial port connection (19200-8-N-1) to the Kaleidescape component. ## Discovery -Manually initiated Auto-discovery will locate all supported Kaleidescape components if they are on the same IP subnet of the openHAB server. +Auto-discovery is supported for Alto and Strato components if the device can be located on the local network using UPnP. +Manually initiated discovery will locate all legacy Premiere line components if they are on the same IP subnet of the openHAB server. In the Inbox, select Search For Things and then choose the Kaleidescape Binding to initiate a discovery scan. ## Thing Configuration @@ -42,7 +43,6 @@ The thing has the following configuration parameters: Some notes: -- Due to a bug in the control protocol, a Strato C player will be identified as a Premiere 'Player' by the auto discovery process. - The only caveat of note about this binding is the updatePeriod configuration parameter. - When set to the default of 0, the component only sends running time update messages sporadically (as an example: when the movie chapter changes) while content is playing. - In this case, the running time channels will also only sporadically update. diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/feature/feature.xml b/bundles/org.openhab.binding.kaleidescape/src/main/feature/feature.xml index 6066d53f018..cfe4e85e0ba 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.kaleidescape/src/main/feature/feature.xml @@ -5,6 +5,7 @@ openhab-runtime-base openhab-transport-serial + openhab-transport-upnp mvn:org.openhab.addons.bundles/org.openhab.binding.kaleidescape/${project.version} diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java index e3ab0fb5b25..0c77a722082 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/KaleidescapeBindingConstants.java @@ -35,6 +35,10 @@ public class KaleidescapeBindingConstants { public static final ThingTypeUID THING_TYPE_ALTO = new ThingTypeUID(BINDING_ID, "alto"); public static final ThingTypeUID THING_TYPE_STRATO = new ThingTypeUID(BINDING_ID, "strato"); + // Constants related to discovery + public static final String PROPERTY_UUID = "uuid"; + public static final String PROPERTY_HOST_NAME = "host"; + public static final String PROPERTY_PORT_NUM = "port"; public static final int DEFAULT_API_PORT = 10000; public static final int DISCOVERY_THREAD_POOL_SIZE = 15; public static final boolean DISCOVERY_DEFAULT_AUTO_DISCOVER = false; diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/communication/KaleidescapeStatusCodes.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/communication/KaleidescapeStatusCodes.java index 95031d2636c..77337ff298e 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/communication/KaleidescapeStatusCodes.java +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/communication/KaleidescapeStatusCodes.java @@ -76,44 +76,63 @@ public class KaleidescapeStatusCodes { static { VIDEO_MODE.put("00", "No output"); - VIDEO_MODE.put("01", "480i60 4:3"); - VIDEO_MODE.put("02", "480i60 16:9"); - VIDEO_MODE.put("03", "480p60 4:3"); - VIDEO_MODE.put("04", "480p60 16:9"); - VIDEO_MODE.put("05", "576i50 4:3"); - VIDEO_MODE.put("06", "576i50 16:9"); - VIDEO_MODE.put("07", "576p50 4:3"); - VIDEO_MODE.put("08", "576p50 16:9"); - VIDEO_MODE.put("09", "720p60 NTSC HD"); - VIDEO_MODE.put("10", "720p50 PAL HD"); - VIDEO_MODE.put("11", "1080i60 16:9"); - VIDEO_MODE.put("12", "1080i50 16:9"); - VIDEO_MODE.put("13", "1080p60 16:9"); - VIDEO_MODE.put("14", "1080p50 16:9"); - VIDEO_MODE.put("15", RESERVED); - VIDEO_MODE.put("16", RESERVED); - VIDEO_MODE.put("17", "1080p24 16:9"); - VIDEO_MODE.put("18", RESERVED); - VIDEO_MODE.put("19", "480i60 64:27"); - VIDEO_MODE.put("20", "576i50 64:27"); - VIDEO_MODE.put("21", "1080i60 64:27"); - VIDEO_MODE.put("22", "1080i50 64:27"); - VIDEO_MODE.put("23", "1080p60 64:27"); - VIDEO_MODE.put("24", "1080p50 64:27"); - VIDEO_MODE.put("25", "1080p24 64:27"); - VIDEO_MODE.put("26", "1080p24 64:27"); - VIDEO_MODE.put("27", "3840x 2160p24 16:9"); - VIDEO_MODE.put("28", "3840x 2160p24 64:27"); - VIDEO_MODE.put("29", "3840x 2160p30 16:9"); - VIDEO_MODE.put("30", "3840x 2160p30 64:27"); - VIDEO_MODE.put("31", "3840x 2160p60 16:9"); - VIDEO_MODE.put("32", "3840x 2160p60 64:27"); - VIDEO_MODE.put("33", "3840x 2160p25 16:9"); - VIDEO_MODE.put("34", "3840x 2160p25 64:27"); - VIDEO_MODE.put("35", "3840x 2160p50 16:9"); - VIDEO_MODE.put("36", "3840x 2160p50 64:27"); - VIDEO_MODE.put("37", "3840x 2160p24 16:9"); - VIDEO_MODE.put("38", "3840x 2160p24 64:27"); + VIDEO_MODE.put("01", "720x480i59.94 4:3"); + VIDEO_MODE.put("02", "720x480i59.94 16:9"); + VIDEO_MODE.put("03", "720x480p59.94 4:3"); + VIDEO_MODE.put("04", "720x480p59.94 16:9"); + VIDEO_MODE.put("05", "720x576i50 4:3"); + VIDEO_MODE.put("06", "720x576i50 16:9"); + VIDEO_MODE.put("07", "720x576p50 4:3"); + VIDEO_MODE.put("08", "720x576p50 16:9"); + VIDEO_MODE.put("09", "1280x720p59.94 NTSC HD"); + VIDEO_MODE.put("10", "1280x720p50 PAL HD"); + VIDEO_MODE.put("11", "1920x1080i59.94 16:9"); + VIDEO_MODE.put("12", "1920x1080i50 16:9"); + VIDEO_MODE.put("13", "1920x1080p59.94 16:9"); + VIDEO_MODE.put("14", "1920x1080p50 16:9"); + VIDEO_MODE.put("15", "1280x720p23.976 16:9"); + VIDEO_MODE.put("16", "1280x720p24 16:9"); + VIDEO_MODE.put("17", "1920x1080p23.976 16:9"); + VIDEO_MODE.put("18", "1920x1080p24 16:9"); + VIDEO_MODE.put("19", "720x480i59.94 64:27"); + VIDEO_MODE.put("20", "720x576i50 64:27"); + VIDEO_MODE.put("21", "1920x1080i59.94 64:27"); + VIDEO_MODE.put("22", "1920x1080i50 64:27"); + VIDEO_MODE.put("23", "1920x1080p59.94 64:27"); + VIDEO_MODE.put("24", "1920x1080p50 64:27"); + VIDEO_MODE.put("25", "1920x1080p23.976 64:27"); + VIDEO_MODE.put("26", "1920x1080p24 64:27"); + VIDEO_MODE.put("27", "3840x2160p23.976 16:9"); + VIDEO_MODE.put("28", "3840x2160p23.976 64:27"); + VIDEO_MODE.put("29", "3840x2160p29.97 16:9"); + VIDEO_MODE.put("30", "3840x2160p29.97 64:27"); + VIDEO_MODE.put("31", "3840x2160p59.94 16:9"); + VIDEO_MODE.put("32", "3840x2160p59.94 64:27"); + VIDEO_MODE.put("33", "3840x2160p25 16:9"); + VIDEO_MODE.put("34", "3840x2160p25 64:27"); + VIDEO_MODE.put("35", "3840x2160p50 16:9"); + VIDEO_MODE.put("36", "3840x2160p50 64:27"); + VIDEO_MODE.put("37", "3840x2160p24 16:9"); + VIDEO_MODE.put("38", "3840x2160p24 64:27"); + VIDEO_MODE.put("39", "1280x720p60 16:9"); + VIDEO_MODE.put("40", "1920x1080i60 16:9"); + VIDEO_MODE.put("41", "1920x1080i60 64:27"); + VIDEO_MODE.put("42", "1920x1080p60 16:9"); + VIDEO_MODE.put("43", "1920x1080p60 64:27"); + VIDEO_MODE.put("44", "3840x2160p 16:9"); + VIDEO_MODE.put("45", "3840x2160p 64:27"); + VIDEO_MODE.put("46", "1280x720p25 16:9"); + VIDEO_MODE.put("47", "1920x1080p25 16:9"); + VIDEO_MODE.put("48", "1920x1080p25 64:27"); + VIDEO_MODE.put("49", RESERVED); + VIDEO_MODE.put("50", "1280x720p29.97 16:9"); + VIDEO_MODE.put("51", "1920x1080p29.97 16:9"); + VIDEO_MODE.put("52", "1920x1080p29.97 64:27"); + VIDEO_MODE.put("53", "1280x720p30 16:9"); + VIDEO_MODE.put("54", "1920x1080p30 16:9"); + VIDEO_MODE.put("55", "1920x1080p30 64:27"); + VIDEO_MODE.put("56", "3840x2160p30 16:9"); + VIDEO_MODE.put("57", "3840x2160p30 64:27"); } // map to lookup eotf @@ -121,8 +140,11 @@ public class KaleidescapeStatusCodes { static { EOTF.put("00", UNKNOWN); EOTF.put("01", "SDR"); - EOTF.put("02", "HDR"); - EOTF.put("03", "SMTPE ST 2048"); + EOTF.put("02", RESERVED); + EOTF.put("03", "HDR10"); + EOTF.put("04", RESERVED); + EOTF.put("05", "Dolby Vision - standard"); + EOTF.put("03", "Dolby Vision - low-latency"); } // map to lookup readiness state diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryParticipant.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryParticipant.java new file mode 100644 index 00000000000..ef1d0d45fab --- /dev/null +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryParticipant.java @@ -0,0 +1,106 @@ +/** + * 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.kaleidescape.internal.discovery; + +import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*; + +import java.util.HashMap; +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; + +/** + * The {@link KaleidescapeDiscoveryParticipant} class discovers Strato/Encore line components automatically via UPnP. + * + * @author Michael Lobstein - Initial contribution + * + */ +@NonNullByDefault +@Component(immediate = true) +public class KaleidescapeDiscoveryParticipant implements UpnpDiscoveryParticipant { + private final Logger logger = LoggerFactory.getLogger(KaleidescapeDiscoveryParticipant.class); + + private static final String MANUFACTURER = "Kaleidescape"; + + // Component Types + private static final String ALTO = "Alto"; + private static final String STRATO = "Strato"; + + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(THING_TYPE_ALTO, THING_TYPE_STRATO); + } + + @Override + public @Nullable DiscoveryResult createResult(RemoteDevice device) { + final ThingUID uid = getThingUID(device); + if (uid != null) { + final Map properties = new HashMap<>(3); + final String label; + + if (device.getDetails().getFriendlyName() != null && !device.getDetails().getFriendlyName().isBlank()) { + label = device.getDetails().getFriendlyName(); + } else { + label = device.getDetails().getModelDetails().getModelName(); + } + + properties.put(PROPERTY_UUID, uid.getId()); + properties.put(PROPERTY_HOST_NAME, device.getIdentity().getDescriptorURL().getHost()); + properties.put(PROPERTY_PORT_NUM, DEFAULT_API_PORT); + + 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().getModelName() != null + && device.getDetails().getManufacturerDetails().getManufacturer().startsWith(MANUFACTURER)) { + final String modelName = device.getDetails().getModelDetails().getModelName(); + final String id = device.getIdentity().getUdn().getIdentifierString().replace(":", EMPTY); + + logger.debug("Kaleidescape {} with id {} found at {}", modelName, id, + device.getIdentity().getDescriptorURL().getHost()); + + if (id.isBlank()) { + logger.debug("Invalid UDN for Kaleidescape device: {}", device.toString()); + return null; + } + + if (modelName.contains(ALTO)) { + return new ThingUID(THING_TYPE_ALTO, id); + } else if (modelName.contains(STRATO)) { + return new ThingUID(THING_TYPE_STRATO, id); + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryService.java b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryService.java index 7c9994dda42..b8d786bf56b 100644 --- a/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryService.java +++ b/bundles/org.openhab.binding.kaleidescape/src/main/java/org/openhab/binding/kaleidescape/internal/discovery/KaleidescapeDiscoveryService.java @@ -25,16 +25,12 @@ import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -50,7 +46,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link KaleidescapeDiscoveryService} class allow manual discovery of Kaleidescape components. + * The {@link KaleidescapeDiscoveryService} class allows manual discovery of legacy Premiere line components. * * @author Chris Graham - Initial contribution * @author Michael Lobstein - Adapted for the Kaleidescape binding @@ -60,22 +56,17 @@ import org.slf4j.LoggerFactory; @Component(service = DiscoveryService.class, configurationPid = "discovery.kaleidescape") public class KaleidescapeDiscoveryService extends AbstractDiscoveryService { private final Logger logger = LoggerFactory.getLogger(KaleidescapeDiscoveryService.class); - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(THING_TYPE_PLAYER, THING_TYPE_CINEMA_ONE, THING_TYPE_ALTO, THING_TYPE_STRATO) - .collect(Collectors.toSet())); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PLAYER, + THING_TYPE_CINEMA_ONE); private static final int K_HEARTBEAT_PORT = 1443; // Component Types private static final String PLAYER = "Player"; private static final String CINEMA_ONE = "Cinema One"; - private static final String ALTO = "Alto"; - private static final String STRATO = "Strato"; - private static final String STRATO_S = "Strato S"; private static final String DISC_VAULT = "Disc Vault"; - private static final Set ALLOWED_DEVICES = new HashSet<>( - Arrays.asList(PLAYER, CINEMA_ONE, ALTO, STRATO, STRATO_S, DISC_VAULT)); + private static final Set ALLOWED_DEVICES = Set.of(PLAYER, CINEMA_ONE, DISC_VAULT); @Nullable private ExecutorService executorService = null; @@ -201,13 +192,11 @@ public class KaleidescapeDiscoveryService extends AbstractDiscoveryService { BufferedReader reader = new BufferedReader(new InputStreamReader(input)); - ThingTypeUID thingTypeUid = THING_TYPE_PLAYER; String friendlyName = EMPTY; String serialNumber = EMPTY; String componentType = EMPTY; String line; String videoZone = null; - String audioZone = null; int lineCount = 0; while ((line = reader.readLine()) != null) { @@ -217,7 +206,6 @@ public class KaleidescapeDiscoveryService extends AbstractDiscoveryService { switch (strArr[1]) { case "NUM_ZONES": videoZone = strArr[2]; - audioZone = strArr[3]; break; case "DEVICE_TYPE_NAME": componentType = strArr[2]; @@ -241,39 +229,13 @@ public class KaleidescapeDiscoveryService extends AbstractDiscoveryService { } } - // see if we have a video zone - if ("01".equals(videoZone)) { - // now check if we are one of the allowed types - if (ALLOWED_DEVICES.contains(componentType)) { - if (STRATO_S.equals(componentType) || STRATO.equals(componentType)) { - thingTypeUid = THING_TYPE_STRATO; - } + // see if we have a video zone and are one of the allowed types + if ("01".equals(videoZone) && ALLOWED_DEVICES.contains(componentType)) { + // default THING_TYPE_PLAYER for Any KPlayer, M Class [M300, M500, M700] or Cinema One 1st Gen + // Cinema One 2nd Gen uses THING_TYPE_CINEMA_ONE - // A 'Player' without an audio zone is really a Strato C - // does not work yet, Strato C erroneously reports "01" for audio zones - // so we are unable to differentiate a Strato C from a Premiere player - if ("00".equals(audioZone) && PLAYER.equals(componentType)) { - thingTypeUid = THING_TYPE_STRATO; - } - - // Alto - if (ALTO.equals(componentType)) { - thingTypeUid = THING_TYPE_ALTO; - } - - // Cinema One - if (CINEMA_ONE.equals(componentType)) { - thingTypeUid = THING_TYPE_CINEMA_ONE; - } - - // A Disc Vault with a video zone (the M700 vault), just call it a THING_TYPE_PLAYER - if (DISC_VAULT.equals(componentType)) { - thingTypeUid = THING_TYPE_PLAYER; - } - - // default THING_TYPE_PLAYER - submitDiscoveryResults(thingTypeUid, ipAddress, friendlyName, serialNumber); - } + submitDiscoveryResults(CINEMA_ONE.equals(componentType) ? THING_TYPE_CINEMA_ONE : THING_TYPE_PLAYER, + ipAddress, friendlyName, serialNumber); } else { logger.debug("No Suitable Kaleidescape component found at IP address ({})", ipAddress); } @@ -300,10 +262,10 @@ public class KaleidescapeDiscoveryService extends AbstractDiscoveryService { HashMap properties = new HashMap<>(); - properties.put("host", ip); - properties.put("port", DEFAULT_API_PORT); + properties.put(PROPERTY_HOST_NAME, ip); + properties.put(PROPERTY_PORT_NUM, DEFAULT_API_PORT); - thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withRepresentationProperty("host") - .withLabel(friendlyName).build()); + thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties) + .withRepresentationProperty(PROPERTY_HOST_NAME).withLabel(friendlyName).build()); } }