2 * Copyright (c) 2010-2021 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.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
30 import org.openhab.core.config.discovery.DiscoveryResult;
31 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
32 import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.osgi.service.component.annotations.Component;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The {@link SoundTouchDiscoveryParticipant} is responsible processing the
42 * results of searches for mDNS services of type _soundtouch._tcp.local.
44 * @author Christian Niessner - Initial contribution
45 * @author Thomas Traunbauer - Initial contribution
47 @Component(configurationPid = "discovery.bosesoundtouch")
48 public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
50 private final Logger logger = LoggerFactory.getLogger(SoundTouchDiscoveryParticipant.class);
53 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
54 return SUPPORTED_THING_TYPES_UIDS;
58 public DiscoveryResult createResult(ServiceInfo info) {
59 DiscoveryResult result = null;
60 ThingUID uid = getThingUID(info);
62 // remove the domain from the name
63 InetAddress[] addrs = info.getInetAddresses();
65 Map<String, Object> properties = new HashMap<>(2);
68 if (BST_10_THING_TYPE_UID.equals(getThingTypeUID(info))) {
70 String group = DiscoveryUtil.executeUrl("http://" + addrs[0].getHostAddress() + ":8090/getGroup");
71 label = DiscoveryUtil.getContentOfFirstElement(group, "name");
72 } catch (IOException e) {
73 logger.debug("Can't obtain label for group. Will use the default one");
77 if (label == null || label.isEmpty()) {
78 label = info.getName();
81 if (label == null || label.isEmpty()) {
82 label = "Bose SoundTouch";
85 // we expect only one address per device..
86 if (addrs.length > 1) {
87 logger.warn("Bose SoundTouch device {} ({}) reports multiple addresses - using the first one: {}",
88 info.getName(), label, Arrays.toString(addrs));
91 properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress());
92 if (getMacAddress(info) != null) {
93 properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS,
94 new String(getMacAddress(info), StandardCharsets.UTF_8));
97 // Set manufacturer as thing property (if available)
98 byte[] manufacturer = info.getPropertyBytes("MANUFACTURER");
99 if (manufacturer != null) {
100 properties.put(Thing.PROPERTY_VENDOR, new String(manufacturer, StandardCharsets.UTF_8));
102 return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).withTTL(600).build();
108 public ThingUID getThingUID(ServiceInfo info) {
109 logger.trace("ServiceInfo: {}", info);
110 ThingTypeUID typeUID = getThingTypeUID(info);
111 if (typeUID != null) {
112 if (info.getType() != null) {
113 if (info.getType().equals(getServiceType())) {
114 logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName());
115 byte[] mac = getMacAddress(info);
117 return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
128 public String getServiceType() {
129 return "_soundtouch._tcp.local.";
132 private ThingTypeUID getThingTypeUID(ServiceInfo info) {
133 InetAddress[] addrs = info.getInetAddresses();
134 if (addrs.length > 0) {
135 String ip = addrs[0].getHostAddress();
136 String deviceId = null;
137 byte[] mac = getMacAddress(info);
139 deviceId = new String(mac, StandardCharsets.UTF_8);
143 String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
144 deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
145 } catch (IOException e) {
149 if (deviceType.toLowerCase().contains("soundtouch 10")) {
150 // Check if it's a Stereo Pair
152 String group = DiscoveryUtil.executeUrl("http://" + ip + ":8090/getGroup");
153 String masterDevice = DiscoveryUtil.getContentOfFirstElement(group, "masterDeviceId");
155 if (Objects.equals(deviceId, masterDevice)) {
156 // Stereo Pair - Master Device
157 return BST_10_THING_TYPE_UID;
158 } else if (!masterDevice.isEmpty()) {
159 // Stereo Pair - Secondary Device - should not be paired
163 return BST_10_THING_TYPE_UID;
165 } catch (IOException e) {
169 if (deviceType.toLowerCase().contains("soundtouch 20")) {
170 return BST_20_THING_TYPE_UID;
172 if (deviceType.toLowerCase().contains("soundtouch 300")) {
173 return BST_300_THING_TYPE_UID;
175 if (deviceType.toLowerCase().contains("soundtouch 30")) {
176 return BST_30_THING_TYPE_UID;
178 if (deviceType.toLowerCase().contains("soundtouch wireless link adapter")) {
179 return BST_WLA_THING_TYPE_UID;
181 if (deviceType.toLowerCase().contains("wave")) {
182 return BST_WSMS_THING_TYPE_UID;
184 if (deviceType.toLowerCase().contains("amplifier")) {
185 return BST_SA5A_THING_TYPE_UID;
192 private byte[] getMacAddress(ServiceInfo info) {
194 // sometimes we see empty messages - ignore them
195 if (!info.hasData()) {
198 byte[] mac = info.getPropertyBytes("MAC");
200 logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
203 if (mac.length != 12) {
204 BigInteger bi = new BigInteger(1, mac);
205 logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(),
206 String.format("%0" + (mac.length << 1) + "X", bi));