Skip to content

Commit

Permalink
Improve discovery
Browse files Browse the repository at this point in the history
Resolves openhab#16690

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
  • Loading branch information
jlaur committed Apr 27, 2024
1 parent 229c2b7 commit 960918d
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 49 deletions.
Expand Up @@ -4,6 +4,7 @@

<feature name="openhab-binding-denonmarantz" description="Denon / Marantz 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.denonmarantz/${project.version}</bundle>
</feature>
</features>
Expand Up @@ -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");

Expand Down
Expand Up @@ -22,6 +22,8 @@

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;
Expand All @@ -36,32 +38,28 @@
* @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.";

/**
* 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";
private static final String DENON_MODEL_PREFIX = "denon";

/**
* Marantz AVRs have a MAC address / serial number starting with 000678
*/
private static final String MARANTZ_MAC_PREFIX = "000678";
private static final String MARANTZ_MODEL_PREFIX = "marantz";

@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
Expand All @@ -74,55 +72,58 @@ public String getServiceType() {
}

@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();
if (thingUID == null) {
return null;
}

/**
* 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<String, Object> 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);
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();

String label = friendlyName + " (" + vendor + ' ' + model + ")";
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(label)
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
// '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<String, Object> properties = new HashMap<>(3);

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()) {
Expand Down
@@ -0,0 +1,83 @@
/**
* 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.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<ThingTypeUID> getSupportedThingTypeUIDs() {
return Set.of(THING_TYPE_AVR);
}

@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
logger.debug("AVR found: {}", device.getDisplayString());
ThingUID thingUID = getThingUID(device);
if (thingUID == null) {
return null;
}

DeviceDetails details = device.getDetails();
String host = details.getBaseURL().getHost();
String model = details.getModelDetails().getModelName();
String serialNumber = details.getSerialNumber().toLowerCase();

Map<String, Object> properties = new HashMap<>(3);
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);

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;
}
return new ThingUID(THING_TYPE_AVR, details.getSerialNumber().toLowerCase());
}
}

0 comments on commit 960918d

Please sign in to comment.