Skip to content

Commit 3baaeb6

Browse files
authored
Improve discovery (openhab#16692)
Resolves openhab#16690 Signed-off-by: Jacob Laursen <[email protected]>
1 parent 22e3d55 commit 3baaeb6

File tree

4 files changed

+162
-51
lines changed

4 files changed

+162
-51
lines changed

bundles/org.openhab.binding.denonmarantz/src/main/feature/feature.xml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
<feature name="openhab-binding-denonmarantz" description="Denon / Marantz Binding" version="${project.version}">
66
<feature>openhab-runtime-base</feature>
7+
<feature>openhab-transport-upnp</feature>
78
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.denonmarantz/${project.version}</bundle>
89
</feature>
910
</features>

bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/DenonMarantzBindingConstants.java

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public class DenonMarantzBindingConstants {
3030

3131
public static final String BINDING_ID = "denonmarantz";
3232

33+
public static final String VENDOR_DENON = "Denon";
34+
public static final String VENDOR_MARANTZ = "Marantz";
35+
3336
// List of all Thing Type UIDs
3437
public static final ThingTypeUID THING_TYPE_AVR = new ThingTypeUID(BINDING_ID, "avr");
3538

Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
import javax.jmdns.ServiceInfo;
2424

25+
import org.eclipse.jdt.annotation.NonNullByDefault;
26+
import org.eclipse.jdt.annotation.Nullable;
2527
import org.openhab.core.config.discovery.DiscoveryResult;
2628
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
2729
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
@@ -33,35 +35,32 @@
3335
import org.slf4j.LoggerFactory;
3436

3537
/**
36-
* @author Jan-Willem Veldhuis - Initial contribution
38+
* The {@link DenonMarantzMDNSDiscoveryParticipant} is responsible for discovering Denon/Marantz AV Receivers.
39+
* It uses the central {@link org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant}.
3740
*
41+
* @author Jan-Willem Veldhuis - Initial contribution
3842
*/
39-
@Component
40-
public class DenonMarantzDiscoveryParticipant implements MDNSDiscoveryParticipant {
43+
@Component(configurationPid = "discovery.denonmarantz")
44+
@NonNullByDefault
45+
public class DenonMarantzMDNSDiscoveryParticipant implements MDNSDiscoveryParticipant {
4146

42-
private Logger logger = LoggerFactory.getLogger(DenonMarantzDiscoveryParticipant.class);
47+
private Logger logger = LoggerFactory.getLogger(DenonMarantzMDNSDiscoveryParticipant.class);
4348

4449
// Service type for 'Airplay enabled' receivers
4550
private static final String RAOP_SERVICE_TYPE = "_raop._tcp.local.";
4651

4752
/**
4853
* Match the serial number, vendor and model of the discovered AVR.
4954
* Input is like "0006781D58B1@Marantz SR5008._raop._tcp.local."
50-
* A Denon AVR serial (MAC address) starts with 0005CD
51-
* A Marantz AVR serial (MAC address) starts with 000678
55+
* Older Denon AVR's serial (MAC address) starts with 0005CD
56+
* Older Marantz AVR's serial (MAC address) starts with 000678
57+
* Newer Denon AVR's can start with 000678 as well.
5258
*/
5359
private static final Pattern DENON_MARANTZ_PATTERN = Pattern
5460
.compile("^((?:0005CD|000678)[A-Z0-9]+)@(.+)\\._raop\\._tcp\\.local\\.$");
5561

56-
/**
57-
* Denon AVRs have a MAC address / serial number starting with 0005CD
58-
*/
59-
private static final String DENON_MAC_PREFIX = "0005CD";
60-
61-
/**
62-
* Marantz AVRs have a MAC address / serial number starting with 000678
63-
*/
64-
private static final String MARANTZ_MAC_PREFIX = "000678";
62+
private static final String DENON_MODEL_PREFIX = "denon";
63+
private static final String MARANTZ_MODEL_PREFIX = "marantz";
6564

6665
@Override
6766
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
@@ -74,55 +73,58 @@ public String getServiceType() {
7473
}
7574

7675
@Override
76+
@Nullable
7777
public DiscoveryResult createResult(ServiceInfo serviceInfo) {
7878
String qualifiedName = serviceInfo.getQualifiedName();
7979
logger.debug("AVR found: {}", qualifiedName);
8080
ThingUID thingUID = getThingUID(serviceInfo);
81-
if (thingUID != null) {
82-
Matcher matcher = DENON_MARANTZ_PATTERN.matcher(qualifiedName);
83-
matcher.matches(); // we already know it matches, it was matched in getThingUID
84-
String serial = matcher.group(1).toLowerCase();
81+
if (thingUID == null) {
82+
return null;
83+
}
8584

86-
/**
87-
* The Vendor is not available from the mDNS result.
88-
* We assign the Vendor based on our assumptions of the MAC address prefix.
89-
*/
90-
String vendor = "";
91-
if (serial.startsWith(MARANTZ_MAC_PREFIX)) {
92-
vendor = "Marantz";
93-
} else if (serial.startsWith(DENON_MAC_PREFIX)) {
94-
vendor = "Denon";
95-
}
96-
97-
// 'am=...' property describes the model name
98-
String model = serviceInfo.getPropertyString("am");
99-
String friendlyName = matcher.group(2).trim();
100-
101-
Map<String, Object> properties = new HashMap<>(2);
102-
103-
if (serviceInfo.getHostAddresses().length == 0) {
104-
logger.debug("Could not determine IP address for the Denon/Marantz AVR");
105-
return null;
106-
}
107-
String host = serviceInfo.getHostAddresses()[0];
108-
109-
logger.debug("IP Address: {}", host);
110-
111-
properties.put(PARAMETER_HOST, host);
112-
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial);
113-
properties.put(Thing.PROPERTY_VENDOR, vendor);
114-
properties.put(Thing.PROPERTY_MODEL_ID, model);
85+
Matcher matcher = DENON_MARANTZ_PATTERN.matcher(qualifiedName);
86+
matcher.matches(); // we already know it matches, it was matched in getThingUID
87+
String serial = matcher.group(1).toLowerCase();
11588

116-
String label = friendlyName + " (" + vendor + ' ' + model + ")";
117-
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(label)
118-
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
89+
// 'am=...' property describes the model name
90+
String model = serviceInfo.getPropertyString("am");
91+
String friendlyName = matcher.group(2).trim();
11992

93+
String vendor;
94+
String modelLowerCase = model == null ? "" : model.toLowerCase();
95+
if (modelLowerCase.startsWith(DENON_MODEL_PREFIX)) {
96+
vendor = VENDOR_DENON;
97+
} else if (modelLowerCase.startsWith(MARANTZ_MODEL_PREFIX)) {
98+
vendor = VENDOR_MARANTZ;
12099
} else {
100+
vendor = null;
101+
}
102+
103+
Map<String, Object> properties = new HashMap<>(4);
104+
105+
if (serviceInfo.getHostAddresses().length == 0) {
106+
logger.debug("Could not determine IP address for the Denon/Marantz AVR");
121107
return null;
122108
}
109+
String host = serviceInfo.getHostAddresses()[0];
110+
111+
logger.debug("IP Address: {}", host);
112+
113+
properties.put(PARAMETER_HOST, host);
114+
if (vendor != null) {
115+
properties.put(Thing.PROPERTY_VENDOR, vendor);
116+
}
117+
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial);
118+
if (model != null) {
119+
properties.put(Thing.PROPERTY_MODEL_ID, model);
120+
}
121+
122+
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(friendlyName)
123+
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
123124
}
124125

125126
@Override
127+
@Nullable
126128
public ThingUID getThingUID(ServiceInfo service) {
127129
Matcher matcher = DENON_MARANTZ_PATTERN.matcher(service.getQualifiedName());
128130
if (matcher.matches()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.denonmarantz.internal.discovery;
14+
15+
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.*;
16+
17+
import java.net.URL;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.Set;
21+
22+
import org.eclipse.jdt.annotation.NonNullByDefault;
23+
import org.eclipse.jdt.annotation.Nullable;
24+
import org.jupnp.model.meta.DeviceDetails;
25+
import org.jupnp.model.meta.RemoteDevice;
26+
import org.openhab.core.config.discovery.DiscoveryResult;
27+
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
28+
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
29+
import org.openhab.core.thing.Thing;
30+
import org.openhab.core.thing.ThingTypeUID;
31+
import org.openhab.core.thing.ThingUID;
32+
import org.osgi.service.component.annotations.Component;
33+
import org.slf4j.Logger;
34+
import org.slf4j.LoggerFactory;
35+
36+
/**
37+
* The {@link DenonMarantzUpnpDiscoveryParticipant} is responsible for discovering Denon AV Receivers.
38+
* It uses the central {@link org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService}.
39+
*
40+
* @author Jacob Laursen - Initial contribution
41+
*/
42+
@Component(configurationPid = "discovery.denonmarantz")
43+
@NonNullByDefault
44+
public class DenonMarantzUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
45+
46+
private Logger logger = LoggerFactory.getLogger(DenonMarantzUpnpDiscoveryParticipant.class);
47+
48+
@Override
49+
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
50+
return Set.of(THING_TYPE_AVR);
51+
}
52+
53+
@Override
54+
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
55+
DeviceDetails details = device.getDetails();
56+
57+
if (!VENDOR_DENON.equalsIgnoreCase(details.getManufacturerDetails().getManufacturer())) {
58+
return null;
59+
}
60+
61+
URL baseUrl = details.getBaseURL();
62+
if (baseUrl == null) {
63+
logger.debug("Discovered {}, but base URL is missing", device.getDisplayString());
64+
return null;
65+
}
66+
67+
String serialNumber = details.getSerialNumber();
68+
if (serialNumber == null) {
69+
logger.debug("Discovered {}, but serial number is missing", device.getDisplayString());
70+
return null;
71+
}
72+
73+
ThingUID thingUID = getThingUID(device);
74+
if (thingUID == null) {
75+
return null;
76+
}
77+
78+
String host = baseUrl.getHost();
79+
String model = details.getModelDetails().getModelName();
80+
81+
logger.debug("Discovered {}", device.getDisplayString());
82+
83+
Map<String, Object> properties = new HashMap<>(4);
84+
properties.put(PARAMETER_HOST, host);
85+
properties.put(Thing.PROPERTY_VENDOR, VENDOR_DENON);
86+
properties.put(Thing.PROPERTY_MODEL_ID, model);
87+
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber.toLowerCase());
88+
89+
return DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(VENDOR_DENON + " " + model)
90+
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
91+
}
92+
93+
@Override
94+
public @Nullable ThingUID getThingUID(RemoteDevice device) {
95+
DeviceDetails details = device.getDetails();
96+
if (!VENDOR_DENON.equalsIgnoreCase(details.getManufacturerDetails().getManufacturer())) {
97+
return null;
98+
}
99+
String serialNumber = details.getSerialNumber();
100+
if (serialNumber == null) {
101+
return null;
102+
}
103+
return new ThingUID(THING_TYPE_AVR, serialNumber.toLowerCase());
104+
}
105+
}

0 commit comments

Comments
 (0)