]> git.basschouten.com Git - openhab-addons.git/blob
e1cc1bbefa413581ebed46520eac69e85642dbad
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.bosesoundtouch.internal.discovery;
14
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
16
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;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Set;
26
27 import javax.jmdns.ServiceInfo;
28
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;
41
42 /**
43  * The {@link SoundTouchDiscoveryParticipant} is responsible processing the
44  * results of searches for mDNS services of type _soundtouch._tcp.local.
45  *
46  * @author Christian Niessner - Initial contribution
47  * @author Thomas Traunbauer - Initial contribution
48  */
49 @NonNullByDefault
50 @Component(configurationPid = "discovery.bosesoundtouch")
51 public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
52
53     private final Logger logger = LoggerFactory.getLogger(SoundTouchDiscoveryParticipant.class);
54
55     @Override
56     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
57         return SUPPORTED_THING_TYPES_UIDS;
58     }
59
60     @Override
61     public @Nullable DiscoveryResult createResult(ServiceInfo info) {
62         DiscoveryResult result = null;
63         ThingUID uid = getThingUID(info);
64         if (uid != null) {
65             // remove the domain from the name
66             InetAddress[] addrs = info.getInetAddresses();
67
68             Map<String, Object> properties = new HashMap<>(2);
69
70             String label = null;
71             if (BST_10_THING_TYPE_UID.equals(getThingTypeUID(info))) {
72                 try {
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");
77                 }
78             }
79
80             if (label == null || label.isEmpty()) {
81                 label = info.getName();
82             }
83
84             if (label == null || label.isEmpty()) {
85                 label = "Bose SoundTouch";
86             }
87
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));
92             }
93
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));
99             }
100
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));
105             }
106             return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).withTTL(600).build();
107         }
108         return result;
109     }
110
111     @Override
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));
122                     }
123                 }
124             }
125         }
126         return null;
127     }
128
129     @Override
130     public String getServiceType() {
131         return "_soundtouch._tcp.local.";
132     }
133
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);
142             }
143             String deviceType;
144             try {
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());
149                 return null;
150             }
151
152             if (deviceType.toLowerCase().contains("soundtouch 10")) {
153                 // Check if it's a Stereo Pair
154                 try {
155                     String group = DiscoveryUtil.executeUrl("http://" + ip + ":8090/getGroup");
156                     String masterDevice = DiscoveryUtil.getContentOfFirstElement(group, "masterDeviceId");
157
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
163                         return null;
164                     } else {
165                         // Single player
166                         return BST_10_THING_TYPE_UID;
167                     }
168                 } catch (IOException e) {
169                     logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
170                     return null;
171                 }
172             }
173             if (deviceType.toLowerCase().contains("soundtouch 20")) {
174                 return BST_20_THING_TYPE_UID;
175             }
176             if (deviceType.toLowerCase().contains("soundtouch 300")) {
177                 return BST_300_THING_TYPE_UID;
178             }
179             if (deviceType.toLowerCase().contains("soundtouch 30")) {
180                 return BST_30_THING_TYPE_UID;
181             }
182             if (deviceType.toLowerCase().contains("soundtouch wireless link adapter")) {
183                 return BST_WLA_THING_TYPE_UID;
184             }
185             if (deviceType.toLowerCase().contains("wave")) {
186                 return BST_WSMS_THING_TYPE_UID;
187             }
188             if (deviceType.toLowerCase().contains("amplifier")) {
189                 return BST_SA5A_THING_TYPE_UID;
190             }
191             return null;
192         }
193         return null;
194     }
195
196     private byte[] getMacAddress(ServiceInfo info) {
197         // sometimes we see empty messages - ignore them
198         if (!info.hasData()) {
199             return new byte[0];
200         }
201         byte[] mac = info.getPropertyBytes("MAC");
202         if (mac == null) {
203             logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
204             return new byte[0];
205         }
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));
210             return new byte[0];
211         }
212         return mac;
213     }
214 }