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