]> git.basschouten.com Git - openhab-addons.git/blob
61589f82903db5db40bc32f5334c331584d32d0f
[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.mpd.internal.handler;
14
15 import static org.openhab.binding.mpd.internal.MPDBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.mpd.internal.MPDBindingConstants;
28 import org.openhab.binding.mpd.internal.MPDConfiguration;
29 import org.openhab.binding.mpd.internal.action.MPDActions;
30 import org.openhab.binding.mpd.internal.protocol.MPDConnection;
31 import org.openhab.binding.mpd.internal.protocol.MPDSong;
32 import org.openhab.binding.mpd.internal.protocol.MPDStatus;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.IncreaseDecreaseType;
35 import org.openhab.core.library.types.NextPreviousType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.PercentType;
38 import org.openhab.core.library.types.PlayPauseType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.thing.binding.ThingHandlerService;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.State;
49 import org.openhab.core.types.UnDefType;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * The {@link MPDHandler} is responsible for handling commands, which are
55  * sent to one of the channels.
56  *
57  * @author Stefan Röllin - Initial contribution
58  */
59 @NonNullByDefault
60 public class MPDHandler extends BaseThingHandler implements MPDEventListener {
61
62     private final Logger logger = LoggerFactory.getLogger(MPDHandler.class);
63
64     private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
65
66     private final MPDConnection connection;
67     private int volume = 0;
68
69     private @Nullable ScheduledFuture<?> futureUpdateStatus;
70     private @Nullable ScheduledFuture<?> futureUpdateCurrentSong;
71
72     public MPDHandler(Thing thing) {
73         super(thing);
74         connection = new MPDConnection(this);
75     }
76
77     @Override
78     public void handleCommand(ChannelUID channelUID, Command command) {
79         if (command instanceof RefreshType) {
80             handleCommandRefresh(channelUID.getId());
81         } else {
82             handlePlayerCommand(channelUID.getId(), command);
83         }
84     }
85
86     @Override
87     public void initialize() {
88         MPDConfiguration config = getConfigAs(MPDConfiguration.class);
89         String uniquePropVal = String.format("%s-%d", config.getIpAddress(), config.getPort());
90         updateProperty(MPDBindingConstants.UNIQUE_ID, uniquePropVal);
91
92         updateStatus(ThingStatus.UNKNOWN);
93         connection.start(config.getIpAddress(), config.getPort(), config.getPassword(),
94                 "OH-binding-" + getThing().getUID().getAsString());
95     }
96
97     @Override
98     public void dispose() {
99         ScheduledFuture<?> future = this.futureUpdateStatus;
100         if (future != null) {
101             future.cancel(true);
102         }
103
104         future = this.futureUpdateCurrentSong;
105         if (future != null) {
106             future.cancel(true);
107         }
108
109         connection.dispose();
110         super.dispose();
111     }
112
113     @Override
114     public Collection<Class<? extends ThingHandlerService>> getServices() {
115         return Set.of(MPDActions.class);
116     }
117
118     /**
119      * send a command to the music player daemon
120      *
121      * @param command command to send
122      * @param parameter parameter of command
123      */
124     public void sendCommand(@Nullable String command, String... parameter) {
125         if (command != null) {
126             connection.sendCommand(command, parameter);
127         } else {
128             logger.warn("can't send null command");
129         }
130     }
131
132     private void handleCommandRefresh(String channelId) {
133         stateMap.remove(channelId);
134         switch (channelId) {
135             case CHANNEL_CONTROL:
136             case CHANNEL_STOP:
137             case CHANNEL_VOLUME:
138                 scheduleUpdateStatus();
139                 break;
140             case CHANNEL_CURRENT_ALBUM:
141             case CHANNEL_CURRENT_ARTIST:
142             case CHANNEL_CURRENT_NAME:
143             case CHANNEL_CURRENT_SONG:
144             case CHANNEL_CURRENT_SONG_ID:
145             case CHANNEL_CURRENT_TITLE:
146             case CHANNEL_CURRENT_TRACK:
147                 scheduleUpdateCurrentSong();
148                 break;
149         }
150     }
151
152     private synchronized void scheduleUpdateStatus() {
153         logger.debug("scheduleUpdateStatus");
154         ScheduledFuture<?> future = this.futureUpdateStatus;
155         if (future == null || future.isCancelled() || future.isDone()) {
156             this.futureUpdateStatus = scheduler.schedule(this::doUpdateStatus, 100, TimeUnit.MILLISECONDS);
157         }
158     }
159
160     private void doUpdateStatus() {
161         connection.updateStatus();
162     }
163
164     private synchronized void scheduleUpdateCurrentSong() {
165         logger.debug("scheduleUpdateCurrentSong");
166         ScheduledFuture<?> future = this.futureUpdateCurrentSong;
167         if (future == null || future.isCancelled() || future.isDone()) {
168             this.futureUpdateCurrentSong = scheduler.schedule(this::doUpdateCurrentSong, 100, TimeUnit.MILLISECONDS);
169         }
170     }
171
172     private void doUpdateCurrentSong() {
173         connection.updateCurrentSong();
174     }
175
176     private void handlePlayerCommand(String channelId, Command command) {
177         switch (channelId) {
178             case CHANNEL_CONTROL:
179                 handleCommandControl(command);
180                 break;
181             case CHANNEL_STOP:
182                 handleCommandStop(command);
183                 break;
184             case CHANNEL_VOLUME:
185                 handleCommandVolume(command);
186                 break;
187         }
188     }
189
190     private void handleCommandControl(Command command) {
191         if (command instanceof PlayPauseType) {
192             if (command == PlayPauseType.PLAY) {
193                 connection.play();
194             } else if (command == PlayPauseType.PAUSE) {
195                 connection.pause();
196             }
197         } else if (command instanceof NextPreviousType) {
198             if (command == NextPreviousType.NEXT) {
199                 connection.playNext();
200             } else if (command == NextPreviousType.PREVIOUS) {
201                 connection.playPrevious();
202             }
203         } else {
204             // Rewind and Fast Forward are currently not implemented by the binding
205             logger.debug("Control command {} is not supported", command);
206         }
207     }
208
209     private void handleCommandStop(Command command) {
210         if (command instanceof OnOffType) {
211             if (command == OnOffType.ON) {
212                 connection.stop();
213             } else if (command == OnOffType.OFF) {
214                 connection.play();
215             }
216         } else {
217             logger.debug("Stop Command {} is not supported", command);
218             return;
219         }
220     }
221
222     private void handleCommandVolume(Command command) {
223         int newValue = 0;
224         if (command instanceof IncreaseDecreaseType) {
225             if (command == IncreaseDecreaseType.INCREASE) {
226                 newValue = Math.min(100, volume + 1);
227             } else if (command == IncreaseDecreaseType.DECREASE) {
228                 newValue = Math.max(0, volume - 1);
229             }
230         } else if (command instanceof OnOffType) {
231             if (command == OnOffType.ON) {
232                 newValue = 100;
233             } else if (command == OnOffType.OFF) {
234                 newValue = 0;
235             }
236         } else if (command instanceof DecimalType decimalCommand) {
237             newValue = decimalCommand.intValue();
238         } else if (command instanceof PercentType percentCommand) {
239             newValue = percentCommand.intValue();
240         } else {
241             logger.debug("Command {} is not supported to change volume", command);
242             return;
243         }
244
245         connection.setVolume(newValue);
246     }
247
248     private void updateChannel(String channelID, State state) {
249         State previousState = stateMap.put(channelID, state);
250         if (previousState == null || !previousState.equals(state)) {
251             updateState(channelID, state);
252         }
253     }
254
255     @Override
256     public void updateMPDStatus(MPDStatus status) {
257         volume = status.getVolume();
258         if (volume < 0 || volume > 100) {
259             updateChannel(CHANNEL_VOLUME, UnDefType.UNDEF);
260         } else {
261             updateChannel(CHANNEL_VOLUME, new PercentType(volume));
262         }
263
264         State newControlState = UnDefType.UNDEF;
265         switch (status.getState()) {
266             case PLAY:
267                 newControlState = PlayPauseType.PLAY;
268                 break;
269             case STOP:
270             case PAUSE:
271                 newControlState = PlayPauseType.PAUSE;
272                 break;
273         }
274         updateChannel(CHANNEL_CONTROL, newControlState);
275
276         State newStopState = OnOffType.OFF;
277         if (status.getState() == MPDStatus.State.STOP) {
278             newStopState = OnOffType.ON;
279         }
280         updateChannel(CHANNEL_STOP, newStopState);
281     }
282
283     @Override
284     public void updateMPDSong(MPDSong song) {
285         updateChannel(CHANNEL_CURRENT_ALBUM, new StringType(song.getAlbum()));
286         updateChannel(CHANNEL_CURRENT_ARTIST, new StringType(song.getArtist()));
287         updateChannel(CHANNEL_CURRENT_NAME, new StringType(song.getName()));
288         updateChannel(CHANNEL_CURRENT_SONG, new DecimalType(song.getSong()));
289         updateChannel(CHANNEL_CURRENT_SONG_ID, new DecimalType(song.getSongId()));
290         updateChannel(CHANNEL_CURRENT_TITLE, new StringType(song.getTitle()));
291         updateChannel(CHANNEL_CURRENT_TRACK, new DecimalType(song.getTrack()));
292     }
293
294     @Override
295     public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
296         updateStatus(status, statusDetail, description);
297     }
298 }