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