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