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.sonos.internal;
15 import static org.openhab.binding.sonos.internal.SonosBindingConstants.SUPPORTED_THING_TYPES_UIDS;
16 import static org.openhab.binding.sonos.internal.config.ZonePlayerConfiguration.UDN;
18 import java.util.Dictionary;
19 import java.util.Hashtable;
21 import java.util.concurrent.ConcurrentHashMap;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.sonos.internal.handler.ZonePlayerHandler;
26 import org.openhab.core.audio.AudioHTTPServer;
27 import org.openhab.core.audio.AudioSink;
28 import org.openhab.core.config.core.Configuration;
29 import org.openhab.core.io.transport.upnp.UpnpIOService;
30 import org.openhab.core.net.HttpServiceUtil;
31 import org.openhab.core.net.NetworkAddressService;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingRegistry;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.BaseThingHandlerFactory;
37 import org.openhab.core.thing.binding.ThingHandler;
38 import org.openhab.core.thing.binding.ThingHandlerFactory;
39 import org.osgi.framework.ServiceRegistration;
40 import org.osgi.service.component.ComponentContext;
41 import org.osgi.service.component.annotations.Activate;
42 import org.osgi.service.component.annotations.Component;
43 import org.osgi.service.component.annotations.Reference;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link SonosHandlerFactory} is responsible for creating things and thing
51 * @author Karel Goderis - Initial contribution
54 @Component(service = ThingHandlerFactory.class, configurationPid = "binding.sonos")
55 public class SonosHandlerFactory extends BaseThingHandlerFactory {
57 private final Logger logger = LoggerFactory.getLogger(SonosHandlerFactory.class);
59 // Bindings should not use the ThingRegistry! See https://github.com/openhab/openhab-addons/pull/6080 and
60 // https://github.com/eclipse/smarthome/issues/5182
61 private final ThingRegistry thingRegistry;
62 private final UpnpIOService upnpIOService;
63 private final AudioHTTPServer audioHTTPServer;
64 private final NetworkAddressService networkAddressService;
65 private final SonosStateDescriptionOptionProvider stateDescriptionProvider;
67 private final Map<String, ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();
69 // optional OPML URL that can be configured through configuration admin
70 private @Nullable String opmlUrl;
72 // url (scheme+server+port) to use for playing notification sounds
73 private @Nullable String callbackUrl;
76 public SonosHandlerFactory(final @Reference ThingRegistry thingRegistry,
77 final @Reference UpnpIOService upnpIOService, final @Reference AudioHTTPServer audioHTTPServer,
78 final @Reference NetworkAddressService networkAddressService,
79 final @Reference SonosStateDescriptionOptionProvider stateDescriptionProvider) {
80 this.thingRegistry = thingRegistry;
81 this.upnpIOService = upnpIOService;
82 this.audioHTTPServer = audioHTTPServer;
83 this.networkAddressService = networkAddressService;
84 this.stateDescriptionProvider = stateDescriptionProvider;
88 protected void activate(ComponentContext componentContext) {
89 super.activate(componentContext);
90 Dictionary<String, Object> properties = componentContext.getProperties();
91 opmlUrl = (String) properties.get("opmlUrl");
92 callbackUrl = (String) properties.get("callbackUrl");
96 public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
97 @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
98 if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
99 ThingUID sonosDeviceUID = getPlayerUID(thingTypeUID, thingUID, configuration);
100 logger.debug("Creating a sonos thing with ID '{}'", sonosDeviceUID);
101 return super.createThing(thingTypeUID, configuration, sonosDeviceUID, null);
103 throw new IllegalArgumentException(
104 "The thing type " + thingTypeUID + " is not supported by the sonos binding.");
108 public boolean supportsThingType(ThingTypeUID thingTypeUID) {
109 return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
113 protected @Nullable ThingHandler createHandler(Thing thing) {
114 ThingTypeUID thingTypeUID = thing.getThingTypeUID();
116 if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
117 logger.debug("Creating a ZonePlayerHandler for thing '{}' with UDN '{}'", thing.getUID(),
118 thing.getConfiguration().get(UDN));
120 ZonePlayerHandler handler = new ZonePlayerHandler(thingRegistry, thing, upnpIOService, opmlUrl,
121 stateDescriptionProvider);
123 // register the speaker as an audio sink
124 String callbackUrl = createCallbackUrl();
125 SonosAudioSink audioSink = new SonosAudioSink(handler, audioHTTPServer, callbackUrl);
126 @SuppressWarnings("unchecked")
127 ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) getBundleContext()
128 .registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
129 audioSinkRegistrations.put(thing.getUID().toString(), reg);
136 private @Nullable String createCallbackUrl() {
137 if (callbackUrl != null) {
140 final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
141 if (ipAddress == null) {
142 logger.warn("No network interface could be found.");
146 // we do not use SSL as it can cause certificate validation issues.
147 final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
149 logger.warn("Cannot find port of the http service.");
153 return "http://" + ipAddress + ":" + port;
158 public void unregisterHandler(Thing thing) {
159 super.unregisterHandler(thing);
160 ServiceRegistration<AudioSink> reg = audioSinkRegistrations.get(thing.getUID().toString());
166 private ThingUID getPlayerUID(ThingTypeUID thingTypeUID, @Nullable ThingUID thingUID, Configuration configuration) {
167 if (thingUID != null) {
170 String udn = (String) configuration.get(UDN);
171 return new ThingUID(thingTypeUID, udn);