]> git.basschouten.com Git - openhab-addons.git/blob
772a1b366c6247bd15ff0ffa6406a697dc6210eb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.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;
39
40 /**
41  * The {@link SoundTouchDiscoveryParticipant} is responsible processing the
42  * results of searches for mDNS services of type _soundtouch._tcp.local.
43  *
44  * @author Christian Niessner - Initial contribution
45  * @author Thomas Traunbauer - Initial contribution
46  */
47 @Component(configurationPid = "discovery.bosesoundtouch")
48 public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
49
50     private final Logger logger = LoggerFactory.getLogger(SoundTouchDiscoveryParticipant.class);
51
52     @Override
53     public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
54         return SUPPORTED_THING_TYPES_UIDS;
55     }
56
57     @Override
58     public DiscoveryResult createResult(ServiceInfo info) {
59         DiscoveryResult result = null;
60         ThingUID uid = getThingUID(info);
61         if (uid != null) {
62             // remove the domain from the name
63             InetAddress[] addrs = info.getInetAddresses();
64
65             Map<String, Object> properties = new HashMap<>(2);
66
67             String label = null;
68             if (BST_10_THING_TYPE_UID.equals(getThingTypeUID(info))) {
69                 try {
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");
74                 }
75             }
76
77             if (label == null || label.isEmpty()) {
78                 label = info.getName();
79             }
80
81             if (label == null || label.isEmpty()) {
82                 label = "Bose SoundTouch";
83             }
84
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));
89             }
90
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));
95             }
96
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));
101             }
102             return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).withTTL(600).build();
103         }
104         return result;
105     }
106
107     @Override
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);
116                     if (mac != null) {
117                         return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
118                     } else {
119                         return null;
120                     }
121                 }
122             }
123         }
124         return null;
125     }
126
127     @Override
128     public String getServiceType() {
129         return "_soundtouch._tcp.local.";
130     }
131
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);
138             if (mac != null) {
139                 deviceId = new String(mac, StandardCharsets.UTF_8);
140             }
141             String deviceType;
142             try {
143                 String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
144                 deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
145             } catch (IOException e) {
146                 return null;
147             }
148
149             if (deviceType.toLowerCase().contains("soundtouch 10")) {
150                 // Check if it's a Stereo Pair
151                 try {
152                     String group = DiscoveryUtil.executeUrl("http://" + ip + ":8090/getGroup");
153                     String masterDevice = DiscoveryUtil.getContentOfFirstElement(group, "masterDeviceId");
154
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
160                         return null;
161                     } else {
162                         // Single player
163                         return BST_10_THING_TYPE_UID;
164                     }
165                 } catch (IOException e) {
166                     return null;
167                 }
168             }
169             if (deviceType.toLowerCase().contains("soundtouch 20")) {
170                 return BST_20_THING_TYPE_UID;
171             }
172             if (deviceType.toLowerCase().contains("soundtouch 300")) {
173                 return BST_300_THING_TYPE_UID;
174             }
175             if (deviceType.toLowerCase().contains("soundtouch 30")) {
176                 return BST_30_THING_TYPE_UID;
177             }
178             if (deviceType.toLowerCase().contains("soundtouch wireless link adapter")) {
179                 return BST_WLA_THING_TYPE_UID;
180             }
181             if (deviceType.toLowerCase().contains("wave")) {
182                 return BST_WSMS_THING_TYPE_UID;
183             }
184             if (deviceType.toLowerCase().contains("amplifier")) {
185                 return BST_SA5A_THING_TYPE_UID;
186             }
187             return null;
188         }
189         return null;
190     }
191
192     private byte[] getMacAddress(ServiceInfo info) {
193         if (info != null) {
194             // sometimes we see empty messages - ignore them
195             if (!info.hasData()) {
196                 return null;
197             }
198             byte[] mac = info.getPropertyBytes("MAC");
199             if (mac == null) {
200                 logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
201                 return null;
202             }
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));
207                 return null;
208             }
209             return mac;
210         }
211         return null;
212     }
213 }