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.fsinternetradio.internal;
15 import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.HashSet;
23 import org.jupnp.model.meta.DeviceDetails;
24 import org.jupnp.model.meta.ManufacturerDetails;
25 import org.jupnp.model.meta.ModelDetails;
26 import org.jupnp.model.meta.RemoteDevice;
27 import org.jupnp.model.meta.RemoteDeviceIdentity;
28 import org.openhab.core.config.discovery.DiscoveryResult;
29 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
30 import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
31 import org.openhab.core.thing.ThingTypeUID;
32 import org.openhab.core.thing.ThingUID;
33 import org.osgi.service.component.annotations.Component;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * This is the discovery service for internet radios based on the fontier silicon chipset. Unfortunately, it is not
39 * easily possible to detect from the upnp information which devices are supported. So currently, discovery only works
40 * for medion internet radios. {@link FSInternetRadioDiscoveryParticipant#getThingUID(RemoteDevice)} must be extended to
41 * add further supported devices!
43 * @author Patrick Koenemann - Initial contribution
44 * @author Mihaela Memova - removed the getLabel(RemoteDevice device) method due to its unreachable code lines
45 * @author Markus Michels - support for Teufel 3sixty discovery
48 public class FSInternetRadioDiscoveryParticipant implements UpnpDiscoveryParticipant {
49 private final Logger logger = LoggerFactory.getLogger(FSInternetRadioDiscoveryParticipant.class);
51 /** Map from UPnP manufacturer to model number for supported radios; filled in static initializer below. */
52 private static final Map<String, Set<String>> SUPPORTED_RADIO_MODELS = new HashMap<>();
55 // to allow case-insensitive match: add all values UPPER-CASE!
57 // format: MANUFACTURER -> MODEL NAME, as shown e.g. by UPnP Tester as explained here:
58 // https://community.openhab.org/t/internet-radio-i-need-your-help/2131
60 // list of medion internet radios taken from: http://internetradio.medion.com/
61 final Set<String> medionRadios = new HashSet<>();
62 SUPPORTED_RADIO_MODELS.put("MEDION AG", medionRadios);
63 medionRadios.add("MD83813");
64 medionRadios.add("MD84017");
65 medionRadios.add("MD85651");
66 medionRadios.add("MD86062");
67 medionRadios.add("MD86250");
68 medionRadios.add("MD86562");
69 medionRadios.add("MD86672");
70 medionRadios.add("MD86698");
71 medionRadios.add("MD86869");
72 medionRadios.add("MD86891");
73 medionRadios.add("MD86955");
74 medionRadios.add("MD86988");
75 medionRadios.add("MD87090");
76 medionRadios.add("MD87180");
77 medionRadios.add("MD87238");
78 medionRadios.add("MD87267");
80 // list of hama internet radios taken from:
81 // https://www.hama.com/action/searchCtrl/search?searchMode=1&q=Internet%20Radio
82 final Set<String> hamaRadios = new HashSet<>();
83 SUPPORTED_RADIO_MODELS.put("HAMA", hamaRadios);
84 hamaRadios.add("IR100");
85 hamaRadios.add("IR110");
86 hamaRadios.add("IR250");
87 hamaRadios.add("IR320");
88 hamaRadios.add("DIR3000");
89 hamaRadios.add("DIR3100");
90 hamaRadios.add("DIR3110");
92 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/19
93 // and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/20
94 // and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/23
95 // these radios do not provide model number, but the model name should also be ok
96 final Set<String> radiosWithoutManufacturer = new HashSet<>();
97 radiosWithoutManufacturer.add(""); // empty manufacturer / model name
98 radiosWithoutManufacturer.add(null); // missing manufacturer / model name
99 SUPPORTED_RADIO_MODELS.put("SMRS18A1", radiosWithoutManufacturer);
100 SUPPORTED_RADIO_MODELS.put("SMRS30A1", radiosWithoutManufacturer);
101 SUPPORTED_RADIO_MODELS.put("SMRS35A1", radiosWithoutManufacturer);
103 final Set<String> teufelRadios = new HashSet<>();
104 SUPPORTED_RADIO_MODELS.put("Teufel", teufelRadios);
105 teufelRadios.add("Radio 3sixty");
107 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/5
108 final Set<String> ttmicroRadios = new HashSet<>();
109 SUPPORTED_RADIO_MODELS.put("TTMICRO AS", ttmicroRadios);
110 ttmicroRadios.add("PINELL SUPERSOUND");
112 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/7
113 final Set<String> revoRadios = new HashSet<>();
114 SUPPORTED_RADIO_MODELS.put("REVO TECHNOLOGIES LTD", revoRadios);
115 revoRadios.add("S10");
117 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/10
118 // and: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/21
119 final Set<String> robertsRadios = new HashSet<>();
120 SUPPORTED_RADIO_MODELS.put("ROBERTS RADIO LIMITED", robertsRadios);
121 robertsRadios.add("ROBERTS STREAM 93I");
122 robertsRadios.add("ROBERTS STREAM 83I");
124 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/11
125 final Set<String> aunaRadios = new HashSet<>();
126 SUPPORTED_RADIO_MODELS.put("AUNA", aunaRadios);
127 aunaRadios.add("10028154 & 10028155");
128 aunaRadios.add("10028154");
129 aunaRadios.add("10028155");
131 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/22
132 final Set<String> sangeanRadios = new HashSet<>();
133 SUPPORTED_RADIO_MODELS.put("SANGEAN RADIO LIMITED", sangeanRadios);
134 sangeanRadios.add("28");
136 // as reported in: https://community.openhab.org/t/internet-radio-i-need-your-help/2131/25
137 final Set<String> rokuRadios = new HashSet<>();
138 SUPPORTED_RADIO_MODELS.put("ROKU", rokuRadios);
139 rokuRadios.add("M1001");
143 public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
144 return Collections.singleton(THING_TYPE_RADIO);
148 public DiscoveryResult createResult(RemoteDevice device) {
149 final ThingUID uid = getThingUID(device);
151 final Map<String, Object> properties = new HashMap<>(1);
152 final String ip = getIp(device);
154 properties.put(CONFIG_PROPERTY_IP, ip);
156 // add manufacturer and model, if provided
157 final String manufacturer = getManufacturer(device);
158 if (manufacturer != null) {
159 properties.put(PROPERTY_MANUFACTURER, manufacturer);
161 final String dm = getModel(device);
162 final String model = dm != null ? dm : getFriendlyName(device);
164 properties.put(PROPERTY_MODEL, model);
166 final String thingName = (manufacturer == null) && (getModel(device) == null) ? getFriendlyName(device)
167 : device.getDisplayString();
168 return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(thingName).build();
174 private String getManufacturer(RemoteDevice device) {
175 final DeviceDetails details = device.getDetails();
176 if ((details != null) && (details.getManufacturerDetails() != null)) {
177 String manufacturer = details.getManufacturerDetails().getManufacturer().trim();
178 return manufacturer.isEmpty() ? null : manufacturer;
183 private String getModel(RemoteDevice device) {
184 final DeviceDetails details = device.getDetails();
185 if ((details != null) && (details.getModelDetails().getModelNumber() != null)) {
186 String model = details.getModelDetails().getModelNumber().trim();
187 return model.isEmpty() ? null : model;
192 private String getFriendlyName(RemoteDevice device) {
193 final DeviceDetails details = device.getDetails();
194 if ((details != null) && (details.getFriendlyName() != null)) {
195 String name = details.getFriendlyName().trim();
196 return name.isEmpty() ? null : name;
201 private String getIp(RemoteDevice device) {
202 final DeviceDetails details = device.getDetails();
203 if (details != null) {
204 if (details.getBaseURL() != null) {
205 return details.getBaseURL().getHost();
208 final RemoteDeviceIdentity identity = device.getIdentity();
209 if (identity != null) {
210 if (identity.getDescriptorURL() != null) {
211 return identity.getDescriptorURL().getHost();
218 * If <code>device</code> is a supported device, a unique thing ID (e.g. serial number) must be returned. Further
219 * supported devices should be added here, based on the available UPnP information.
221 @SuppressWarnings("null")
223 public ThingUID getThingUID(RemoteDevice device) {
224 final DeviceDetails details = device.getDetails();
225 final String friendlyName = details.getFriendlyName();
226 logger.debug("Discovered unit: {}", friendlyName);
228 if (details != null) {
229 final ManufacturerDetails manufacturerDetails = details.getManufacturerDetails();
230 final ModelDetails modelDetails = details.getModelDetails();
231 if (modelDetails != null) {
232 // check manufacturer and model number
233 final String manufacturer = manufacturerDetails == null ? null : manufacturerDetails.getManufacturer();
234 final String modelNumber = modelDetails.getModelNumber();
235 String serialNumber = details.getSerialNumber();
236 logger.debug("Discovered unit: {} {} - {}", manufacturer, modelNumber, friendlyName);
237 if (modelNumber != null) {
238 if (manufacturer != null) {
239 final Set<String> supportedRadios = SUPPORTED_RADIO_MODELS
240 .get(manufacturer.trim().toUpperCase());
241 if (supportedRadios != null && supportedRadios.contains(modelNumber.toUpperCase())) {
242 return new ThingUID(THING_TYPE_RADIO, serialNumber);
245 // check model name and number
246 final String modelName = modelDetails.getModelName();
247 if (modelName != null) {
248 final Set<String> supportedRadios = SUPPORTED_RADIO_MODELS.get(modelName.trim().toUpperCase());
249 if (supportedRadios != null && supportedRadios.contains(modelNumber.toUpperCase())) {
250 return new ThingUID(THING_TYPE_RADIO, serialNumber);
252 // Teufel reports empty manufacturer and model, but friendly name
253 if (friendlyName.contains("Teufel")) {
254 logger.debug("haha");
256 if (!friendlyName.isEmpty()) {
257 for (Set<String> models : SUPPORTED_RADIO_MODELS.values()) {
258 for (String model : models) {
259 if ((model != null) && !model.isEmpty() && friendlyName.contains(model)) {
260 return new ThingUID(THING_TYPE_RADIO, serialNumber);
268 if (((manufacturer == null) || manufacturer.trim().isEmpty())
269 && ((modelNumber == null) || modelNumber.trim().isEmpty())) {
270 // Some devices report crappy UPnP device description so manufacturer and model are ""
271 // In this case we try to find the match in friendlyName
272 final String uname = friendlyName.toUpperCase();
273 for (Set<String> set : SUPPORTED_RADIO_MODELS.values()) {
274 for (String model : set) {
275 if ((model != null) && !model.isEmpty() && uname.contains(model)) {
276 return new ThingUID(THING_TYPE_RADIO, serialNumber);
282 // maybe we can add further indicators, whether the device is a supported one
284 // device not supported