From 5645dfb73b03575922769250b5e1a27d09b523aa Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Tue, 30 Apr 2024 00:39:30 +0200 Subject: [PATCH] Improve discovery (#16692) Resolves #16690 Signed-off-by: Jacob Laursen Signed-off-by: Ciprian Pascu --- .../src/main/feature/feature.xml | 1 + .../DenonMarantzBindingConstants.java | 3 + ...DenonMarantzMDNSDiscoveryParticipant.java} | 112 +++++++++--------- .../DenonMarantzUpnpDiscoveryParticipant.java | 105 ++++++++++++++++ 4 files changed, 166 insertions(+), 55 deletions(-) rename bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/{DenonMarantzDiscoveryParticipant.java => DenonMarantzMDNSDiscoveryParticipant.java} (55%) create mode 100644 bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzUpnpDiscoveryParticipant.java diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/feature/feature.xml b/bundles/org.openhab.binding.denonmarantz/src/main/feature/feature.xml index 33545862307..107a4f03826 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.denonmarantz/src/main/feature/feature.xml @@ -4,6 +4,7 @@ openhab-runtime-base + openhab-transport-upnp mvn:org.openhab.addons.bundles/org.openhab.binding.denonmarantz/${project.version} diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/DenonMarantzBindingConstants.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/DenonMarantzBindingConstants.java index 58ba6a97116..8da35e91636 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/DenonMarantzBindingConstants.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/DenonMarantzBindingConstants.java @@ -30,6 +30,9 @@ public class DenonMarantzBindingConstants { public static final String BINDING_ID = "denonmarantz"; + public static final String VENDOR_DENON = "Denon"; + public static final String VENDOR_MARANTZ = "Marantz"; + // List of all Thing Type UIDs public static final ThingTypeUID THING_TYPE_AVR = new ThingTypeUID(BINDING_ID, "avr"); diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzDiscoveryParticipant.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzMDNSDiscoveryParticipant.java similarity index 55% rename from bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzDiscoveryParticipant.java rename to bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzMDNSDiscoveryParticipant.java index 11046098d14..663276107cf 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzMDNSDiscoveryParticipant.java @@ -22,6 +22,8 @@ import java.util.regex.Pattern; import javax.jmdns.ServiceInfo; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant; @@ -33,13 +35,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * @author Jan-Willem Veldhuis - Initial contribution + * The {@link DenonMarantzMDNSDiscoveryParticipant} is responsible for discovering Denon/Marantz AV Receivers. + * It uses the central {@link org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant}. * + * @author Jan-Willem Veldhuis - Initial contribution */ -@Component -public class DenonMarantzDiscoveryParticipant implements MDNSDiscoveryParticipant { +@Component(configurationPid = "discovery.denonmarantz") +@NonNullByDefault +public class DenonMarantzMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant { - private Logger logger = LoggerFactory.getLogger(DenonMarantzDiscoveryParticipant.class); + private Logger logger = LoggerFactory.getLogger(DenonMarantzMDNSDiscoveryParticipant.class); // Service type for 'Airplay enabled' receivers private static final String RAOP_SERVICE_TYPE = "_raop._tcp.local."; @@ -47,21 +52,15 @@ public class DenonMarantzDiscoveryParticipant implements MDNSDiscoveryParticipan /** * Match the serial number, vendor and model of the discovered AVR. * Input is like "0006781D58B1@Marantz SR5008._raop._tcp.local." - * A Denon AVR serial (MAC address) starts with 0005CD - * A Marantz AVR serial (MAC address) starts with 000678 + * Older Denon AVR's serial (MAC address) starts with 0005CD + * Older Marantz AVR's serial (MAC address) starts with 000678 + * Newer Denon AVR's can start with 000678 as well. */ private static final Pattern DENON_MARANTZ_PATTERN = Pattern .compile("^((?:0005CD|000678)[A-Z0-9]+)@(.+)\\._raop\\._tcp\\.local\\.$"); - /** - * Denon AVRs have a MAC address / serial number starting with 0005CD - */ - private static final String DENON_MAC_PREFIX = "0005CD"; - - /** - * Marantz AVRs have a MAC address / serial number starting with 000678 - */ - private static final String MARANTZ_MAC_PREFIX = "000678"; + private static final String DENON_MODEL_PREFIX = "denon"; + private static final String MARANTZ_MODEL_PREFIX = "marantz"; @Override public Set getSupportedThingTypeUIDs() { @@ -74,55 +73,58 @@ public class DenonMarantzDiscoveryParticipant implements MDNSDiscoveryParticipan } @Override + @Nullable public DiscoveryResult createResult(ServiceInfo serviceInfo) { String qualifiedName = serviceInfo.getQualifiedName(); logger.debug("AVR found: {}", qualifiedName); ThingUID thingUID = getThingUID(serviceInfo); - if (thingUID != null) { - Matcher matcher = DENON_MARANTZ_PATTERN.matcher(qualifiedName); - matcher.matches(); // we already know it matches, it was matched in getThingUID - String serial = matcher.group(1).toLowerCase(); - - /** - * The Vendor is not available from the mDNS result. - * We assign the Vendor based on our assumptions of the MAC address prefix. - */ - String vendor = ""; - if (serial.startsWith(MARANTZ_MAC_PREFIX)) { - vendor = "Marantz"; - } else if (serial.startsWith(DENON_MAC_PREFIX)) { - vendor = "Denon"; - } - - // 'am=...' property describes the model name - String model = serviceInfo.getPropertyString("am"); - String friendlyName = matcher.group(2).trim(); - - Map properties = new HashMap<>(2); - - if (serviceInfo.getHostAddresses().length == 0) { - logger.debug("Could not determine IP address for the Denon/Marantz AVR"); - return null; - } - String host = serviceInfo.getHostAddresses()[0]; - - logger.debug("IP Address: {}", host); - - properties.put(PARAMETER_HOST, host); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial); - properties.put(Thing.PROPERTY_VENDOR, vendor); - properties.put(Thing.PROPERTY_MODEL_ID, model); - - String label = friendlyName + " (" + vendor + ' ' + model + ")"; - return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(label) - .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build(); - - } else { + if (thingUID == null) { return null; } + + Matcher matcher = DENON_MARANTZ_PATTERN.matcher(qualifiedName); + matcher.matches(); // we already know it matches, it was matched in getThingUID + String serial = matcher.group(1).toLowerCase(); + + // 'am=...' property describes the model name + String model = serviceInfo.getPropertyString("am"); + String friendlyName = matcher.group(2).trim(); + + String vendor; + String modelLowerCase = model == null ? "" : model.toLowerCase(); + if (modelLowerCase.startsWith(DENON_MODEL_PREFIX)) { + vendor = VENDOR_DENON; + } else if (modelLowerCase.startsWith(MARANTZ_MODEL_PREFIX)) { + vendor = VENDOR_MARANTZ; + } else { + vendor = null; + } + + Map properties = new HashMap<>(4); + + if (serviceInfo.getHostAddresses().length == 0) { + logger.debug("Could not determine IP address for the Denon/Marantz AVR"); + return null; + } + String host = serviceInfo.getHostAddresses()[0]; + + logger.debug("IP Address: {}", host); + + properties.put(PARAMETER_HOST, host); + if (vendor != null) { + properties.put(Thing.PROPERTY_VENDOR, vendor); + } + properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial); + if (model != null) { + properties.put(Thing.PROPERTY_MODEL_ID, model); + } + + return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(friendlyName) + .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build(); } @Override + @Nullable public ThingUID getThingUID(ServiceInfo service) { Matcher matcher = DENON_MARANTZ_PATTERN.matcher(service.getQualifiedName()); if (matcher.matches()) { diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzUpnpDiscoveryParticipant.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzUpnpDiscoveryParticipant.java new file mode 100644 index 00000000000..6db4e558f33 --- /dev/null +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/discovery/DenonMarantzUpnpDiscoveryParticipant.java @@ -0,0 +1,105 @@ +/** + * 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.denonmarantz.internal.discovery; + +import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.*; + +import java.net.URL; +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.DeviceDetails; +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.Thing; +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 DenonMarantzUpnpDiscoveryParticipant} is responsible for discovering Denon AV Receivers. + * It uses the central {@link org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService}. + * + * @author Jacob Laursen - Initial contribution + */ +@Component(configurationPid = "discovery.denonmarantz") +@NonNullByDefault +public class DenonMarantzUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant { + + private Logger logger = LoggerFactory.getLogger(DenonMarantzUpnpDiscoveryParticipant.class); + + @Override + public Set getSupportedThingTypeUIDs() { + return Set.of(THING_TYPE_AVR); + } + + @Override + public @Nullable DiscoveryResult createResult(RemoteDevice device) { + DeviceDetails details = device.getDetails(); + + if (!VENDOR_DENON.equalsIgnoreCase(details.getManufacturerDetails().getManufacturer())) { + return null; + } + + URL baseUrl = details.getBaseURL(); + if (baseUrl == null) { + logger.debug("Discovered {}, but base URL is missing", device.getDisplayString()); + return null; + } + + String serialNumber = details.getSerialNumber(); + if (serialNumber == null) { + logger.debug("Discovered {}, but serial number is missing", device.getDisplayString()); + return null; + } + + ThingUID thingUID = getThingUID(device); + if (thingUID == null) { + return null; + } + + String host = baseUrl.getHost(); + String model = details.getModelDetails().getModelName(); + + logger.debug("Discovered {}", device.getDisplayString()); + + Map properties = new HashMap<>(4); + properties.put(PARAMETER_HOST, host); + properties.put(Thing.PROPERTY_VENDOR, VENDOR_DENON); + properties.put(Thing.PROPERTY_MODEL_ID, model); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber.toLowerCase()); + + return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(VENDOR_DENON + " " + model) + .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build(); + } + + @Override + public @Nullable ThingUID getThingUID(RemoteDevice device) { + DeviceDetails details = device.getDetails(); + if (!VENDOR_DENON.equalsIgnoreCase(details.getManufacturerDetails().getManufacturer())) { + return null; + } + String serialNumber = details.getSerialNumber(); + if (serialNumber == null) { + return null; + } + return new ThingUID(THING_TYPE_AVR, serialNumber.toLowerCase()); + } +}