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;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
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;
54 * The {@link MPDHandler} is responsible for handling commands, which are
55 * sent to one of the channels.
57 * @author Stefan Röllin - Initial contribution
60 public class MPDHandler extends BaseThingHandler implements MPDEventListener {
62 private final Logger logger = LoggerFactory.getLogger(MPDHandler.class);
64 private Map<String, State> stateMap = Collections.synchronizedMap(new HashMap<>());
66 private final MPDConnection connection;
67 private int volume = 0;
69 private @Nullable ScheduledFuture<?> futureUpdateStatus;
70 private @Nullable ScheduledFuture<?> futureUpdateCurrentSong;
72 public MPDHandler(Thing thing) {
74 connection = new MPDConnection(this);
78 public void handleCommand(ChannelUID channelUID, Command command) {
79 if (command instanceof RefreshType) {
80 handleCommandRefresh(channelUID.getId());
82 handlePlayerCommand(channelUID.getId(), command);
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);
92 updateStatus(ThingStatus.UNKNOWN);
93 connection.start(config.getIpAddress(), config.getPort(), config.getPassword(),
94 "OH-binding-" + getThing().getUID().getAsString());
98 public void dispose() {
99 ScheduledFuture<?> future = this.futureUpdateStatus;
100 if (future != null) {
104 future = this.futureUpdateCurrentSong;
105 if (future != null) {
109 connection.dispose();
114 public Collection<Class<? extends ThingHandlerService>> getServices() {
115 return Set.of(MPDActions.class);
119 * send a command to the music player daemon
121 * @param command command to send
122 * @param parameter parameter of command
124 public void sendCommand(@Nullable String command, String... parameter) {
125 if (command != null) {
126 connection.sendCommand(command, parameter);
128 logger.warn("can't send null command");
132 private void handleCommandRefresh(String channelId) {
133 stateMap.remove(channelId);
135 case CHANNEL_CONTROL:
138 scheduleUpdateStatus();
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();
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);
160 private void doUpdateStatus() {
161 connection.updateStatus();
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);
172 private void doUpdateCurrentSong() {
173 connection.updateCurrentSong();
176 private void handlePlayerCommand(String channelId, Command command) {
178 case CHANNEL_CONTROL:
179 handleCommandControl(command);
182 handleCommandStop(command);
185 handleCommandVolume(command);
190 private void handleCommandControl(Command command) {
191 if (command instanceof PlayPauseType) {
192 if (command == PlayPauseType.PLAY) {
194 } else if (command == PlayPauseType.PAUSE) {
197 } else if (command instanceof NextPreviousType) {
198 if (command == NextPreviousType.NEXT) {
199 connection.playNext();
200 } else if (command == NextPreviousType.PREVIOUS) {
201 connection.playPrevious();
204 // Rewind and Fast Forward are currently not implemented by the binding
205 logger.debug("Control command {} is not supported", command);
209 private void handleCommandStop(Command command) {
210 if (command instanceof OnOffType) {
211 if (command == OnOffType.ON) {
213 } else if (command == OnOffType.OFF) {
217 logger.debug("Stop Command {} is not supported", command);
222 private void handleCommandVolume(Command command) {
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);
230 } else if (command instanceof OnOffType) {
231 if (command == OnOffType.ON) {
233 } else if (command == OnOffType.OFF) {
236 } else if (command instanceof DecimalType decimalCommand) {
237 newValue = decimalCommand.intValue();
238 } else if (command instanceof PercentType percentCommand) {
239 newValue = percentCommand.intValue();
241 logger.debug("Command {} is not supported to change volume", command);
245 connection.setVolume(newValue);
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);
256 public void updateMPDStatus(MPDStatus status) {
257 volume = status.getVolume();
258 if (volume < 0 || volume > 100) {
259 updateChannel(CHANNEL_VOLUME, UnDefType.UNDEF);
261 updateChannel(CHANNEL_VOLUME, new PercentType(volume));
264 State newControlState = UnDefType.UNDEF;
265 switch (status.getState()) {
267 newControlState = PlayPauseType.PLAY;
271 newControlState = PlayPauseType.PAUSE;
274 updateChannel(CHANNEL_CONTROL, newControlState);
276 State newStopState = OnOffType.OFF;
277 if (status.getState() == MPDStatus.State.STOP) {
278 newStopState = OnOffType.ON;
280 updateChannel(CHANNEL_STOP, newStopState);
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()));
295 public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
296 updateStatus(status, statusDetail, description);