2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bosesoundtouch.internal.discovery;
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
17 import java.io.IOException;
18 import java.math.BigInteger;
19 import java.net.InetAddress;
20 import java.nio.charset.StandardCharsets;
21 import java.util.Arrays;
22 import java.util.HashMap;
24 import java.util.Objects;
27 import javax.jmdns.ServiceInfo;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
32 import org.openhab.core.config.discovery.DiscoveryResult;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.osgi.service.component.annotations.Component;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link SoundTouchDiscoveryParticipant} is responsible processing the
44 * results of searches for mDNS services of type _soundtouch._tcp.local.
46 * @author Christian Niessner - Initial contribution
47 * @author Thomas Traunbauer - Initial contribution
50 @Component(configurationPid = "discovery.bosesoundtouch")
51 public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
53 private final Logger logger = LoggerFactory.getLogger(SoundTouchDiscoveryParticipant.class);
56 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
57 return SUPPORTED_THING_TYPES_UIDS;
61 public @Nullable DiscoveryResult createResult(ServiceInfo info) {
62 DiscoveryResult result = null;
63 ThingUID uid = getThingUID(info);
65 // remove the domain from the name
66 InetAddress[] addrs = info.getInetAddresses();
68 Map<String, Object> properties = new HashMap<>(2);
71 if (BST_10_THING_TYPE_UID.equals(getThingTypeUID(info))) {
73 String group = DiscoveryUtil.executeUrl("http://" + addrs[0].getHostAddress() + ":8090/getGroup");
74 label = DiscoveryUtil.getContentOfFirstElement(group, "name");
75 } catch (IOException e) {
76 logger.debug("Can't obtain label for group. Will use the default one");
80 if (label == null || label.isEmpty()) {
81 label = info.getName();
84 if (label == null || label.isEmpty()) {
85 label = "Bose SoundTouch";
88 // we expect only one address per device..
89 if (addrs.length > 1) {
90 logger.warn("Bose SoundTouch device {} ({}) reports multiple addresses - using the first one: {}",
91 info.getName(), label, Arrays.toString(addrs));
94 properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress());
95 byte[] localMacAddress = getMacAddress(info);
96 if (localMacAddress.length > 0) {
97 properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS,
98 new String(localMacAddress, StandardCharsets.UTF_8));
101 // Set manufacturer as thing property (if available)
102 byte[] manufacturer = info.getPropertyBytes("MANUFACTURER");
103 if (manufacturer != null) {
104 properties.put(Thing.PROPERTY_VENDOR, new String(manufacturer, StandardCharsets.UTF_8));
106 return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).withTTL(600).build();
112 public @Nullable ThingUID getThingUID(ServiceInfo info) {
113 logger.trace("ServiceInfo: {}", info);
114 ThingTypeUID typeUID = getThingTypeUID(info);
115 if (typeUID != null) {
116 if (info.getType() != null) {
117 if (info.getType().equals(getServiceType())) {
118 logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName());
119 byte[] mac = getMacAddress(info);
120 if (mac.length > 0) {
121 return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
130 public String getServiceType() {
131 return "_soundtouch._tcp.local.";
134 private @Nullable ThingTypeUID getThingTypeUID(ServiceInfo info) {
135 InetAddress[] addrs = info.getInetAddresses();
136 if (addrs.length > 0) {
137 String ip = addrs[0].getHostAddress();
138 String deviceId = null;
139 byte[] mac = getMacAddress(info);
140 if (mac.length > 0) {
141 deviceId = new String(mac, StandardCharsets.UTF_8);
145 String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
146 deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
147 } catch (IOException e) {
148 logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
152 if (deviceType.toLowerCase().contains("soundtouch 10")) {
153 // Check if it's a Stereo Pair
155 String group = DiscoveryUtil.executeUrl("http://" + ip + ":8090/getGroup");
156 String masterDevice = DiscoveryUtil.getContentOfFirstElement(group, "masterDeviceId");
158 if (Objects.equals(deviceId, masterDevice)) {
159 // Stereo Pair - Master Device
160 return BST_10_THING_TYPE_UID;
161 } else if (!masterDevice.isEmpty()) {
162 // Stereo Pair - Secondary Device - should not be paired
166 return BST_10_THING_TYPE_UID;
168 } catch (IOException e) {
169 logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
173 if (deviceType.toLowerCase().contains("soundtouch 20")) {
174 return BST_20_THING_TYPE_UID;
176 if (deviceType.toLowerCase().contains("soundtouch 300")) {
177 return BST_300_THING_TYPE_UID;
179 if (deviceType.toLowerCase().contains("soundtouch 30")) {
180 return BST_30_THING_TYPE_UID;
182 if (deviceType.toLowerCase().contains("soundtouch wireless link adapter")) {
183 return BST_WLA_THING_TYPE_UID;
185 if (deviceType.toLowerCase().contains("wave")) {
186 return BST_WSMS_THING_TYPE_UID;
188 if (deviceType.toLowerCase().contains("amplifier")) {
189 return BST_SA5A_THING_TYPE_UID;
196 private byte[] getMacAddress(ServiceInfo info) {
197 // sometimes we see empty messages - ignore them
198 if (!info.hasData()) {
201 byte[] mac = info.getPropertyBytes("MAC");
203 logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
206 if (mac.length != 12) {
207 BigInteger bi = new BigInteger(1, mac);
208 logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(),
209 String.format("%0" + (mac.length << 1) + "X", bi));