]> git.basschouten.com Git - openhab-addons.git/blob
357523051680a3baaa4cf21a79a24d7304fe80bd
[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.pulseaudio.internal.handler;
14
15 import static org.openhab.binding.pulseaudio.internal.PulseaudioBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
26 import org.apache.commons.lang.StringUtils;
27 import org.openhab.binding.pulseaudio.internal.items.AbstractAudioDeviceConfig;
28 import org.openhab.binding.pulseaudio.internal.items.Sink;
29 import org.openhab.binding.pulseaudio.internal.items.SinkInput;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.IncreaseDecreaseType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.PercentType;
35 import org.openhab.core.library.types.StringType;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.thing.ThingUID;
42 import org.openhab.core.thing.binding.BaseThingHandler;
43 import org.openhab.core.thing.binding.ThingHandler;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.openhab.core.types.UnDefType;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * The {@link PulseaudioHandler} is responsible for handling commands, which are
53  * sent to one of the channels.
54  *
55  * @author Tobias Bräutigam - Initial contribution
56  */
57 public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusListener {
58
59     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
60             .unmodifiableSet(Stream.of(SINK_THING_TYPE, COMBINED_SINK_THING_TYPE, SINK_INPUT_THING_TYPE,
61                     SOURCE_THING_TYPE, SOURCE_OUTPUT_THING_TYPE).collect(Collectors.toSet()));
62
63     private int refresh = 60; // refresh every minute as default
64     private ScheduledFuture<?> refreshJob;
65
66     private PulseaudioBridgeHandler bridgeHandler;
67
68     private final Logger logger = LoggerFactory.getLogger(PulseaudioHandler.class);
69
70     private String name;
71
72     public PulseaudioHandler(Thing thing) {
73         super(thing);
74     }
75
76     @Override
77     public void initialize() {
78         Configuration config = getThing().getConfiguration();
79         name = (String) config.get(DEVICE_PARAMETER_NAME);
80
81         // until we get an update put the Thing offline
82         updateStatus(ThingStatus.OFFLINE);
83         deviceOnlineWatchdog();
84     }
85
86     @Override
87     public void dispose() {
88         if (refreshJob != null && !refreshJob.isCancelled()) {
89             refreshJob.cancel(true);
90             refreshJob = null;
91         }
92         updateStatus(ThingStatus.OFFLINE);
93         bridgeHandler = null;
94         logger.trace("Thing {} {} disposed.", getThing().getUID(), name);
95         super.dispose();
96     }
97
98     private void deviceOnlineWatchdog() {
99         Runnable runnable = () -> {
100             try {
101                 PulseaudioBridgeHandler bridgeHandler = getPulseaudioBridgeHandler();
102                 if (bridgeHandler != null) {
103                     if (bridgeHandler.getDevice(name) == null) {
104                         updateStatus(ThingStatus.OFFLINE);
105                         bridgeHandler = null;
106                     } else {
107                         updateStatus(ThingStatus.ONLINE);
108                     }
109                 } else {
110                     logger.debug("Bridge for pulseaudio device {} not found.", name);
111                     updateStatus(ThingStatus.OFFLINE);
112                 }
113             } catch (Exception e) {
114                 logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
115                 bridgeHandler = null;
116             }
117         };
118
119         refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, refresh, TimeUnit.SECONDS);
120     }
121
122     private synchronized PulseaudioBridgeHandler getPulseaudioBridgeHandler() {
123         if (this.bridgeHandler == null) {
124             Bridge bridge = getBridge();
125             if (bridge == null) {
126                 logger.debug("Required bridge not defined for device {}.", name);
127                 return null;
128             }
129             ThingHandler handler = bridge.getHandler();
130             if (handler instanceof PulseaudioBridgeHandler) {
131                 this.bridgeHandler = (PulseaudioBridgeHandler) handler;
132                 this.bridgeHandler.registerDeviceStatusListener(this);
133             } else {
134                 logger.debug("No available bridge handler found for device {} bridge {} .", name, bridge.getUID());
135                 return null;
136             }
137         }
138         return this.bridgeHandler;
139     }
140
141     @Override
142     public void handleCommand(ChannelUID channelUID, Command command) {
143         PulseaudioBridgeHandler bridge = getPulseaudioBridgeHandler();
144         if (bridge == null) {
145             logger.warn("pulseaudio server bridge handler not found. Cannot handle command without bridge.");
146             return;
147         }
148         if (command instanceof RefreshType) {
149             bridge.handleCommand(channelUID, command);
150             return;
151         }
152
153         AbstractAudioDeviceConfig device = bridge.getDevice(name);
154         if (device == null) {
155             logger.warn("device {} not found", name);
156             updateStatus(ThingStatus.OFFLINE);
157             bridgeHandler = null;
158             return;
159         } else {
160             State updateState = UnDefType.UNDEF;
161             if (channelUID.getId().equals(VOLUME_CHANNEL)) {
162                 if (command instanceof IncreaseDecreaseType) {
163                     // refresh to get the current volume level
164                     bridge.getClient().update();
165                     device = bridge.getDevice(name);
166                     int volume = device.getVolume();
167                     if (command.equals(IncreaseDecreaseType.INCREASE)) {
168                         volume = Math.min(100, volume + 5);
169                     }
170                     if (command.equals(IncreaseDecreaseType.DECREASE)) {
171                         volume = Math.max(0, volume - 5);
172                     }
173                     bridge.getClient().setVolumePercent(device, volume);
174                     updateState = new PercentType(volume);
175                 } else if (command instanceof PercentType) {
176                     DecimalType volume = (DecimalType) command;
177                     bridge.getClient().setVolumePercent(device, volume.intValue());
178                     updateState = (PercentType) command;
179                 } else if (command instanceof DecimalType) {
180                     // set volume
181                     DecimalType volume = (DecimalType) command;
182                     bridge.getClient().setVolume(device, volume.intValue());
183                     updateState = (DecimalType) command;
184                 }
185             } else if (channelUID.getId().equals(MUTE_CHANNEL)) {
186                 if (command instanceof OnOffType) {
187                     bridge.getClient().setMute(device, OnOffType.ON.equals(command));
188                     updateState = (OnOffType) command;
189                 }
190             } else if (channelUID.getId().equals(SLAVES_CHANNEL)) {
191                 if (device instanceof Sink && ((Sink) device).isCombinedSink()) {
192                     if (command instanceof StringType) {
193                         List<Sink> slaves = new ArrayList<>();
194                         for (String slaveName : command.toString().split(",")) {
195                             Sink slave = bridge.getClient().getSink(slaveName.trim());
196                             if (slave != null) {
197                                 slaves.add(slave);
198                             }
199                         }
200                         if (!slaves.isEmpty()) {
201                             bridge.getClient().setCombinedSinkSlaves(((Sink) device), slaves);
202                         }
203                     }
204                 } else {
205                     logger.error("{} is no combined sink", device);
206                 }
207             } else if (channelUID.getId().equals(ROUTE_TO_SINK_CHANNEL)) {
208                 if (device instanceof SinkInput) {
209                     Sink newSink = null;
210                     if (command instanceof DecimalType) {
211                         newSink = bridge.getClient().getSink(((DecimalType) command).intValue());
212                     } else {
213                         newSink = bridge.getClient().getSink(command.toString());
214                     }
215                     if (newSink != null) {
216                         logger.debug("rerouting {} to {}", device, newSink);
217                         bridge.getClient().moveSinkInput(((SinkInput) device), newSink);
218                         updateState = new StringType(newSink.getPaName());
219                     } else {
220                         logger.error("no sink {} found", command.toString());
221                     }
222                 }
223             }
224             logger.trace("updating {} to {}", channelUID, updateState);
225             if (!updateState.equals(UnDefType.UNDEF)) {
226                 updateState(channelUID, updateState);
227             }
228         }
229     }
230
231     @Override
232     public void onDeviceStateChanged(ThingUID bridge, AbstractAudioDeviceConfig device) {
233         if (device.getPaName().equals(name)) {
234             updateStatus(ThingStatus.ONLINE);
235             logger.debug("Updating states of {} id: {}", device, VOLUME_CHANNEL);
236             updateState(VOLUME_CHANNEL, new PercentType(device.getVolume()));
237             updateState(MUTE_CHANNEL, device.isMuted() ? OnOffType.ON : OnOffType.OFF);
238             updateState(STATE_CHANNEL,
239                     device.getState() != null ? new StringType(device.getState().toString()) : new StringType("-"));
240             if (device instanceof SinkInput) {
241                 updateState(ROUTE_TO_SINK_CHANNEL,
242                         ((SinkInput) device).getSink() != null
243                                 ? new StringType(((SinkInput) device).getSink().getPaName())
244                                 : new StringType("-"));
245             }
246             if (device instanceof Sink && ((Sink) device).isCombinedSink()) {
247                 updateState(SLAVES_CHANNEL,
248                         new StringType(StringUtils.join(((Sink) device).getCombinedSinkNames(), ",")));
249             }
250         }
251     }
252
253     @Override
254     public void onDeviceRemoved(PulseaudioBridgeHandler bridge, AbstractAudioDeviceConfig device) {
255         if (device.getPaName().equals(name)) {
256             bridgeHandler.unregisterDeviceStatusListener(this);
257             bridgeHandler = null;
258             updateStatus(ThingStatus.OFFLINE);
259         }
260     }
261
262     @Override
263     public void onDeviceAdded(Bridge bridge, AbstractAudioDeviceConfig device) {
264         logger.trace("new device discovered {} by {}", device, bridge);
265     }
266 }