]> git.basschouten.com Git - openhab-addons.git/blob
56367d4a41589fbd3566711f5089e8e9fce45529
[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.pulseaudio.internal.handler;
14
15 import static org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants.*;
16
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.CopyOnWriteArrayList;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfiguration;
30 import org.openhab.binding.pulseaudio.internal.PulseAudioBindingConfigurationListener;
31 import org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants;
32 import org.openhab.binding.pulseaudio.internal.PulseaudioClient;
33 import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
34 import org.openhab.core.config.core.Configuration;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.binding.BaseBridgeHandler;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * {@link PulseaudioBridgeHandler} is the handler for a Pulseaudio server and
50  * connects it to the framework.
51  *
52  * @author Tobias Bräutigam - Initial contribution
53  * @author Gwendal Roulleau - Rewrite for child handler notification
54  *
55  */
56 @NonNullByDefault
57 public class PulseaudioBridgeHandler extends BaseBridgeHandler implements PulseAudioBindingConfigurationListener {
58     private final Logger logger = LoggerFactory.getLogger(PulseaudioBridgeHandler.class);
59
60     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
61             .singleton(PulseaudioBindingConstants.BRIDGE_THING_TYPE);
62
63     public String host = "localhost";
64     public int port = 4712;
65
66     public int refreshInterval = 30000;
67
68     @Nullable
69     private PulseaudioClient client;
70
71     private PulseAudioBindingConfiguration configuration;
72
73     private List<DeviceStatusListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
74     private Set<String> lastActiveDevices = new HashSet<>();
75
76     @Nullable
77     private ScheduledFuture<?> pollingJob;
78
79     private Set<PulseaudioHandler> childHandlersInitialized = new HashSet<>();
80
81     public synchronized void update() {
82         try {
83             getClient().connect();
84         } catch (IOException e) {
85             logger.debug("{}", e.getMessage(), e);
86             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
87                     String.format("Couldn't connect to Pulsaudio server [Host '%s':'%d']: %s", host, port,
88                             e.getMessage() != null ? e.getMessage() : ""));
89             return;
90         }
91
92         getClient().update();
93         if (getThing().getStatus() != ThingStatus.ONLINE) {
94             updateStatus(ThingStatus.ONLINE);
95             logger.debug("Established connection to Pulseaudio server on Host '{}':'{}'.", host, port);
96             // The framework will automatically notify the child handlers as the bridge status is changed
97         } else {
98             // browse all child handlers to update status according to the result of the query to the pulse audio server
99             for (PulseaudioHandler pulseaudioHandler : childHandlersInitialized) {
100                 pulseaudioHandler.deviceUpdate(getDevice(pulseaudioHandler.getDeviceIdentifier()));
101             }
102         }
103         // browse query result to notify add event
104         for (AbstractAudioDeviceConfig device : getClient().getItems()) {
105             if (!lastActiveDevices.contains(device.getPaName())) {
106                 for (DeviceStatusListener deviceStatusListener : deviceStatusListeners) {
107                     try {
108                         deviceStatusListener.onDeviceAdded(getThing(), device);
109                     } catch (Exception e) {
110                         logger.warn("An exception occurred while calling the DeviceStatusListener", e);
111                     }
112                     lastActiveDevices.add(device.getPaName());
113                 }
114             }
115         }
116     }
117
118     public PulseaudioBridgeHandler(Bridge bridge, PulseAudioBindingConfiguration configuration) {
119         super(bridge);
120         this.configuration = configuration;
121     }
122
123     @Override
124     public void handleCommand(ChannelUID channelUID, Command command) {
125         if (command instanceof RefreshType) {
126             getClient().update();
127         } else {
128             logger.debug("received unexpected command for pulseaudio bridge '{}'.", host);
129         }
130     }
131
132     public @Nullable AbstractAudioDeviceConfig getDevice(@Nullable DeviceIdentifier deviceIdentifier) {
133         return deviceIdentifier == null ? null : getClient().getGenericAudioItem(deviceIdentifier);
134     }
135
136     public PulseaudioClient getClient() {
137         PulseaudioClient clientFinal = client;
138         if (clientFinal == null) {
139             throw new AssertionError("PulseaudioClient is null !");
140         }
141         return clientFinal;
142     }
143
144     @Override
145     public void initialize() {
146         logger.debug("Initializing Pulseaudio handler.");
147         Configuration conf = this.getConfig();
148
149         if (conf.get(BRIDGE_PARAMETER_HOST) != null) {
150             this.host = String.valueOf(conf.get(BRIDGE_PARAMETER_HOST));
151         }
152         if (conf.get(BRIDGE_PARAMETER_PORT) != null) {
153             this.port = ((BigDecimal) conf.get(BRIDGE_PARAMETER_PORT)).intValue();
154         }
155         if (conf.get(BRIDGE_PARAMETER_REFRESH_INTERVAL) != null) {
156             this.refreshInterval = ((BigDecimal) conf.get(BRIDGE_PARAMETER_REFRESH_INTERVAL)).intValue();
157         }
158
159         if (!host.isBlank()) {
160             client = new PulseaudioClient(host, port, configuration);
161             updateStatus(ThingStatus.UNKNOWN);
162             final ScheduledFuture<?> pollingJobFinal = pollingJob;
163             if (pollingJobFinal == null || pollingJobFinal.isCancelled()) {
164                 pollingJob = scheduler.scheduleWithFixedDelay(this::update, 0, refreshInterval, TimeUnit.MILLISECONDS);
165             }
166         } else {
167             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, String.format(
168                     "Couldn't connect to Pulseaudio server because of missing connection parameters [Host '%s':'%d']",
169                     host, port));
170         }
171
172         this.configuration.addPulseAudioBindingConfigurationListener(this);
173     }
174
175     @Override
176     public void dispose() {
177         this.configuration.removePulseAudioBindingConfigurationListener(this);
178         ScheduledFuture<?> job = pollingJob;
179         if (job != null) {
180             job.cancel(true);
181             pollingJob = null;
182         }
183         var clientFinal = client;
184         if (clientFinal != null) {
185             clientFinal.disconnect();
186         }
187         super.dispose();
188     }
189
190     public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
191         return deviceStatusListeners.add(deviceStatusListener);
192     }
193
194     public boolean unregisterDeviceStatusListener(DeviceStatusListener deviceStatusListener) {
195         return deviceStatusListeners.remove(deviceStatusListener);
196     }
197
198     @Override
199     public void bindingConfigurationChanged() {
200         // If the bridge thing is not well setup, we do nothing
201         if (getThing().getStatus() != ThingStatus.OFFLINE
202                 || getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) {
203             update();
204         }
205     }
206
207     public void resetKnownActiveDevices() {
208         // If the bridge thing is not well setup, we do nothing
209         if (getThing().getStatus() != ThingStatus.OFFLINE
210                 || getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) {
211             lastActiveDevices = new HashSet<>();
212             update();
213         }
214     }
215
216     @Override
217     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
218         if (childHandler instanceof PulseaudioHandler) {
219             this.childHandlersInitialized.add((PulseaudioHandler) childHandler);
220         } else {
221             logger.error("This bridge can only support PulseaudioHandler child");
222         }
223     }
224
225     @Override
226     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
227         this.childHandlersInitialized.remove(childHandler);
228     }
229 }