/** * Copyright (c) 2010-2018 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.heos.handler; import static org.openhab.binding.heos.HeosBindingConstants.*; import static org.openhab.binding.heos.internal.resources.HeosConstants.*; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.PlayPauseType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.openhab.binding.heos.internal.HeosChannelHandlerFactory; import org.openhab.binding.heos.internal.api.HeosFacade; import org.openhab.binding.heos.internal.api.HeosSystem; import org.openhab.binding.heos.internal.handler.HeosChannelHandler; import org.openhab.binding.heos.internal.resources.HeosEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link HeosThingBaseHandler} class is the base Class all HEOS handler have to extend. * It provides basic command handling and common needed methods. * * @author Johannes Einig - Initial contribution * */ public abstract class HeosThingBaseHandler extends BaseThingHandler implements HeosEventListener { protected String id; protected HeosSystem heos; protected HeosFacade api; protected HeosChannelHandlerFactory channelHandlerFactory; protected HeosBridgeHandler bridge; private long refreshStartTime = 0; private long refreshRequestTime = 0; private final int REFRESH_BLOCK_TIME = 5000; private Logger logger = LoggerFactory.getLogger(this.getClass()); public HeosThingBaseHandler(Thing thing, HeosSystem heos, HeosFacade api) { super(thing); this.heos = heos; this.api = api; setId(); } /** * To be implemented by extending class by the command * for updating the HEOS thing via the {@link HeosSystem}. As example * for a player the command would be {@link HeosSystem#getPlayerState(String)} */ protected abstract void updateHeosThingState(); /** * The channels which has to be updated during the refresh command * For more information about refreshing the channels see * {link {@link HeosThingBaseHandler#handleRefresh()} */ protected abstract void refreshChannels(); public abstract void setStatusOffline(); public abstract PercentType getNotificationSoundVolume(); public abstract void setNotificationSoundVolume(PercentType volume); @Override public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { if (command instanceof RefreshType) { if (this.getThing().getStatus().toString().equals(ONLINE)) { handleRefresh(); } return; } HeosChannelHandler channelHandler = channelHandlerFactory.getChannelHandler(channelUID); if (channelHandler != null) { channelHandler.handleCommand(command, id, this, channelUID); } } /** * Handles the Refresh command. * Because refreshing the channels requires a request of the current state * via the Telnet connection, updating all channels after each other would * lead to a high amount of network traffic. So the handleRefresh() blocks * the update request of {@link HeosThingBaseHandler#updateHeosThingState()}. */ private synchronized void handleRefresh() { refreshRequestTime = System.currentTimeMillis(); if (refreshRequestTime - refreshStartTime > REFRESH_BLOCK_TIME) { updateHeosThingState(); refreshChannels(); refreshStartTime = System.currentTimeMillis(); } } private void setId() { if (thing.getConfiguration().containsKey(GID)) { this.id = thing.getConfiguration().get(GID).toString(); } if (thing.getConfiguration().containsKey(PID)) { this.id = thing.getConfiguration().get(PID).toString(); } } /** * Has to be called by the player or group handler to initialize * {@link HeosChannelHandlerFactory} and bridge */ protected void initChannelHandlerFatory() { if (getBridge() != null) { this.bridge = (HeosBridgeHandler) getBridge().getHandler(); this.channelHandlerFactory = bridge.getChannelHandlerFactory(); } else { logger.warn("No Bridge set within handler"); } } /** * Dispose the handler and unregister the handler * form Change Events */ @Override public void dispose() { api.unregisterforChangeEvents(this); super.dispose(); } /** * Plays a media file from an external source. Can be * used for audio sink function * * @param url The external URL where the file is located */ public void playURL(String urlStr) { try { URL url = new URL(urlStr); api.playURL(id, url); } catch (MalformedURLException e) { logger.debug("Command '{}' is not a propper URL. Error: {}", urlStr, e.getMessage()); } } /** * Handles the updates send from the HEOS system to * the binding. To receive updates the handler has * to register itself via {@link HeosFacade} via the method: * {@link HeosFacade#registerforChangeEvents(HeosEventListener)} * * @param event * @param command */ protected void handleThingStateUpdate(@NonNull String event, @NonNull String command) { if (event.equals(HEOS_STATE)) { switch (command) { case PLAY: updateState(CH_ID_CONTROL, PlayPauseType.PLAY); break; case PAUSE: updateState(CH_ID_CONTROL, PlayPauseType.PAUSE); break; case STOP: updateState(CH_ID_CONTROL, PlayPauseType.PAUSE); break; } } if (event.equals(HEOS_VOLUME)) { updateState(CH_ID_VOLUME, PercentType.valueOf(command)); } if (event.equals(HEOS_MUTE)) { if (command != null) { switch (command) { case ON: updateState(CH_ID_MUTE, OnOffType.ON); break; case OFF: updateState(CH_ID_MUTE, OnOffType.OFF); break; } } } if (event.equals(HEOS_CUR_POS)) { this.updateState(CH_ID_CUR_POS, StringType.valueOf(command)); } if (event.equals(HEOS_DURATION)) { this.updateState(CH_ID_DURATION, StringType.valueOf(command)); } if (event.equals(SHUFFLE_MODE_CHANGED)) { if (command.equals(HEOS_ON)) { this.updateState(CH_ID_SHUFFLE_MODE, OnOffType.ON); } else { this.updateState(CH_ID_SHUFFLE_MODE, OnOffType.OFF); } } if (event.equals(REPEAT_MODE_CHANGED)) { if (command.toString().equals(HEOS_REPEAT_ALL)) { this.updateState(CH_ID_REPEAT_MODE, StringType.valueOf(HEOS_UI_ALL)); } else if (command.toString().equals(HEOS_REPEAT_ONE)) { this.updateState(CH_ID_REPEAT_MODE, StringType.valueOf(HEOS_UI_ONE)); } else if (command.toString().equals(HEOS_OFF)) { this.updateState(CH_ID_REPEAT_MODE, StringType.valueOf(HEOS_UI_OFF)); } } } protected void handleThingMediaUpdate(HashMap<String, String> info) { for (String key : info.keySet()) { switch (key) { case SONG: updateState(CH_ID_SONG, StringType.valueOf(info.get(key))); break; case ARTIST: updateState(CH_ID_ARTIST, StringType.valueOf(info.get(key))); break; case ALBUM: updateState(CH_ID_ALBUM, StringType.valueOf(info.get(key))); break; case IMAGE_URL: updateState(CH_ID_IMAGE_URL, StringType.valueOf(info.get(key))); break; case STATION: if (info.get(SID).equals(INPUT_SID)) { String inputName = info.get(MID).substring(info.get(MID).indexOf("/") + 1); // removes the // "input/" part // before the // input name updateState(CH_ID_INPUTS, StringType.valueOf(inputName)); } updateState(CH_ID_STATION, StringType.valueOf(info.get(key))); break; case TYPE: updateState(CH_ID_TYPE, StringType.valueOf(info.get(key))); if (info.get(key).equals(STATION)) { updateState(CH_ID_STATION, StringType.valueOf(info.get(STATION))); } else { updateState(CH_ID_STATION, StringType.valueOf("No Station")); } break; } } } }