2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mpd.internal.handler;
15 import static org.openhab.binding.mpd.internal.MPDBindingConstants.*;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
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;
53 * The {@link MPDHandler} is responsible for handling commands, which are
54 * sent to one of the channels.
56 * @author Stefan Röllin - Initial contribution
59 public class MPDHandler extends BaseThingHandler implements MPDEventListener {
61 private final Logger logger = LoggerFactory.getLogger(MPDHandler.class);
63 private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
65 private final MPDConnection connection;
66 private int volume = 0;
68 private @Nullable ScheduledFuture<?> futureUpdateStatus;
69 private @Nullable ScheduledFuture<?> futureUpdateCurrentSong;
71 public MPDHandler(Thing thing) {
73 connection = new MPDConnection(this);
77 public void handleCommand(ChannelUID channelUID, Command command) {
78 if (command instanceof RefreshType) {
79 handleCommandRefresh(channelUID.getId());
81 handlePlayerCommand(channelUID.getId(), command);
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);
91 updateStatus(ThingStatus.UNKNOWN);
92 connection.start(config.getIpAddress(), config.getPort(), config.getPassword(),
93 "OH-binding-" + getThing().getUID().getAsString());
97 public void dispose() {
98 ScheduledFuture<?> future = this.futureUpdateStatus;
103 future = this.futureUpdateCurrentSong;
104 if (future != null) {
108 connection.dispose();
113 public Collection<Class<? extends ThingHandlerService>> getServices() {
114 return Collections.singleton(MPDActions.class);
118 * send a command to the music player daemon
120 * @param command command to send
121 * @param parameter parameter of command
123 public void sendCommand(@Nullable String command, String... parameter) {
124 if (command != null) {
125 connection.sendCommand(command, parameter);
127 logger.warn("can't send null command");
131 private void handleCommandRefresh(String channelId) {
132 stateMap.remove(channelId);
134 case CHANNEL_CONTROL:
137 scheduleUpdateStatus();
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();
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);
159 private void doUpdateStatus() {
160 connection.updateStatus();
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);
171 private void doUpdateCurrentSong() {
172 connection.updateCurrentSong();
175 private void handlePlayerCommand(String channelId, Command command) {
177 case CHANNEL_CONTROL:
178 handleCommandControl(command);
181 handleCommandStop(command);
184 handleCommandVolume(command);
189 private void handleCommandControl(Command command) {
190 if (command instanceof PlayPauseType) {
191 if (command == PlayPauseType.PLAY) {
193 } else if (command == PlayPauseType.PAUSE) {
196 } else if (command instanceof NextPreviousType) {
197 if (command == NextPreviousType.NEXT) {
198 connection.playNext();
199 } else if (command == NextPreviousType.PREVIOUS) {
200 connection.playPrevious();
203 // Rewind and Fast Forward are currently not implemented by the binding
204 logger.debug("Control command {} is not supported", command);
208 private void handleCommandStop(Command command) {
209 if (command instanceof OnOffType) {
210 if (command == OnOffType.ON) {
212 } else if (command == OnOffType.OFF) {
216 logger.debug("Stop Command {} is not supported", command);
221 private void handleCommandVolume(Command command) {
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);
229 } else if (command instanceof OnOffType) {
230 if (command == OnOffType.ON) {
232 } else if (command == OnOffType.OFF) {
235 } else if (command instanceof DecimalType) {
236 newValue = ((DecimalType) command).intValue();
237 } else if (command instanceof PercentType) {
238 newValue = ((PercentType) command).intValue();
240 logger.debug("Command {} is not supported to change volume", command);
244 connection.setVolume(newValue);
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);
255 public void updateMPDStatus(MPDStatus status) {
256 volume = status.getVolume();
257 if (volume < 0 || volume > 100) {
258 updateChannel(CHANNEL_VOLUME, UnDefType.UNDEF);
260 updateChannel(CHANNEL_VOLUME, new PercentType(volume));
263 State newControlState = UnDefType.UNDEF;
264 switch (status.getState()) {
266 newControlState = PlayPauseType.PLAY;
270 newControlState = PlayPauseType.PAUSE;
273 updateChannel(CHANNEL_CONTROL, newControlState);
275 State newStopState = OnOffType.OFF;
276 if (status.getState() == MPDStatus.State.STOP) {
277 newStopState = OnOffType.ON;
279 updateChannel(CHANNEL_STOP, newStopState);
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()));
294 public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
295 updateStatus(status, statusDetail, description);