added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.bosesoundtouch-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-bosesoundtouch" description="Bose SoundTouch Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-http</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bosesoundtouch/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link APIRequest} class handles the API requests
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public enum APIRequest {
|
||||
KEY("key"),
|
||||
SELECT("select"),
|
||||
SOURCES("sources"),
|
||||
BASSCAPABILITIES("bassCapabilities"),
|
||||
BASS("bass"),
|
||||
GET_ZONE("getZone"),
|
||||
SET_ZONE("setZone"),
|
||||
ADD_ZONE_SLAVE("addZoneSlave"),
|
||||
REMOVE_ZONE_SLAVE("removeZoneSlave"),
|
||||
NOW_PLAYING("now_playing"),
|
||||
TRACK_INFO("trackInfo"),
|
||||
VOLUME("volume"),
|
||||
PRESETS("presets"),
|
||||
INFO("info"),
|
||||
NAME("name"),
|
||||
GET_GROUP("getGroup");
|
||||
|
||||
private String name;
|
||||
|
||||
private APIRequest(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link AvailableSources} is used to find out, which sources and functions are available
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public interface AvailableSources {
|
||||
|
||||
public boolean isBluetoothAvailable();
|
||||
|
||||
public boolean isAUXAvailable();
|
||||
|
||||
public boolean isAUX1Available();
|
||||
|
||||
public boolean isAUX2Available();
|
||||
|
||||
public boolean isAUX3Available();
|
||||
|
||||
public boolean isTVAvailable();
|
||||
|
||||
public boolean isHDMI1Available();
|
||||
|
||||
public boolean isInternetRadioAvailable();
|
||||
|
||||
public boolean isStoredMusicAvailable();
|
||||
|
||||
public boolean isBassAvailable();
|
||||
|
||||
public void setAUXAvailable(boolean aux);
|
||||
|
||||
public void setAUX1Available(boolean aux1);
|
||||
|
||||
public void setAUX2Available(boolean aux2);
|
||||
|
||||
public void setAUX3Available(boolean aux3);
|
||||
|
||||
public void setStoredMusicAvailable(boolean storedMusic);
|
||||
|
||||
public void setInternetRadioAvailable(boolean internetRadio);
|
||||
|
||||
public void setBluetoothAvailable(boolean bluetooth);
|
||||
|
||||
public void setTVAvailable(boolean tv);
|
||||
|
||||
public void setHDMI1Available(boolean hdmi1);
|
||||
|
||||
public void setBassAvailable(boolean bass);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link BoseSoundTouchBindinConstantsg} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class BoseSoundTouchBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "bosesoundtouch";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID BST_UNKNOWN_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "device");
|
||||
|
||||
public static final ThingTypeUID BST_10_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "10");
|
||||
public static final ThingTypeUID BST_20_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "20");
|
||||
public static final ThingTypeUID BST_30_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "30");
|
||||
public static final ThingTypeUID BST_300_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "300");
|
||||
public static final ThingTypeUID BST_WLA_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "wirelessLinkAdapter");
|
||||
public static final ThingTypeUID BST_WSMS_THING_TYPE_UID = new ThingTypeUID(BINDING_ID,
|
||||
"waveSoundTouchMusicSystemIV");
|
||||
public static final ThingTypeUID BST_SA5A_THING_TYPE_UID = new ThingTypeUID(BINDING_ID, "sa5Amplifier");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_KNOWN_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
|
||||
.of(BST_UNKNOWN_THING_TYPE_UID, BST_10_THING_TYPE_UID, BST_20_THING_TYPE_UID, BST_30_THING_TYPE_UID,
|
||||
BST_300_THING_TYPE_UID, BST_WLA_THING_TYPE_UID, BST_WSMS_THING_TYPE_UID, BST_SA5A_THING_TYPE_UID)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(SUPPORTED_KNOWN_THING_TYPES_UIDS);
|
||||
|
||||
// Partial list of Channel Type IDs
|
||||
public static final String CHANNEL_TYPE_OPERATION_MODE_DEFAULT = "operationMode_default";
|
||||
public static final String CHANNEL_TYPE_OPERATION_MODE_BST_10_20_30 = "operationMode_BST_10_20_30";
|
||||
public static final String CHANNEL_TYPE_OPERATION_MODE_BST_300 = "operationMode_BST_300";
|
||||
public static final String CHANNEL_TYPE_OPERATION_MODE_BST_SA5A = "operationMode_BST_SA5_Amplifier";
|
||||
public static final String CHANNEL_TYPE_OPERATION_MODE_BST_WLA = "operationMode_BST_WLA";
|
||||
|
||||
// List of all Channel IDs
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_VOLUME = "volume";
|
||||
public static final String CHANNEL_MUTE = "mute";
|
||||
public static final String CHANNEL_OPERATIONMODE = "operationMode";
|
||||
public static final String CHANNEL_PLAYER_CONTROL = "playerControl";
|
||||
public static final String CHANNEL_PRESET = "preset";
|
||||
public static final String CHANNEL_BASS = "bass";
|
||||
public static final String CHANNEL_RATEENABLED = "rateEnabled";
|
||||
public static final String CHANNEL_SKIPENABLED = "skipEnabled";
|
||||
public static final String CHANNEL_SKIPPREVIOUSENABLED = "skipPreviousEnabled";
|
||||
public static final String CHANNEL_SAVE_AS_PRESET = "saveAsPreset";
|
||||
public static final String CHANNEL_KEY_CODE = "keyCode";
|
||||
public static final String CHANNEL_NOWPLAYING_ALBUM = "nowPlayingAlbum";
|
||||
public static final String CHANNEL_NOWPLAYING_ARTWORK = "nowPlayingArtwork";
|
||||
public static final String CHANNEL_NOWPLAYING_ARTIST = "nowPlayingArtist";
|
||||
public static final String CHANNEL_NOWPLAYING_DESCRIPTION = "nowPlayingDescription";
|
||||
public static final String CHANNEL_NOWPLAYING_GENRE = "nowPlayingGenre";
|
||||
public static final String CHANNEL_NOWPLAYING_ITEMNAME = "nowPlayingItemName";
|
||||
public static final String CHANNEL_NOWPLAYING_STATIONLOCATION = "nowPlayingStationLocation";
|
||||
public static final String CHANNEL_NOWPLAYING_STATIONNAME = "nowPlayingStationName";
|
||||
public static final String CHANNEL_NOWPLAYING_TRACK = "nowPlayingTrack";
|
||||
public static final String CHANNEL_NOTIFICATION_SOUND = "notificationsound";
|
||||
|
||||
public static final List<String> CHANNEL_IDS = Collections.unmodifiableList(
|
||||
Stream.of(CHANNEL_POWER, CHANNEL_VOLUME, CHANNEL_MUTE, CHANNEL_OPERATIONMODE, CHANNEL_PLAYER_CONTROL,
|
||||
CHANNEL_PRESET, CHANNEL_BASS, CHANNEL_RATEENABLED, CHANNEL_SKIPENABLED, CHANNEL_SKIPPREVIOUSENABLED,
|
||||
CHANNEL_SAVE_AS_PRESET, CHANNEL_KEY_CODE, CHANNEL_NOWPLAYING_ALBUM, CHANNEL_NOWPLAYING_ARTWORK,
|
||||
CHANNEL_NOWPLAYING_ARTIST, CHANNEL_NOWPLAYING_DESCRIPTION, CHANNEL_NOWPLAYING_GENRE,
|
||||
CHANNEL_NOWPLAYING_ITEMNAME, CHANNEL_NOWPLAYING_STATIONLOCATION, CHANNEL_NOWPLAYING_STATIONNAME,
|
||||
CHANNEL_NOWPLAYING_TRACK, CHANNEL_NOTIFICATION_SOUND).collect(Collectors.toList()));
|
||||
|
||||
// Device information parameters;
|
||||
public static final String DEVICE_INFO_NAME = "INFO_NAME";
|
||||
public static final String DEVICE_INFO_TYPE = "INFO_TYPE";
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Configuration class for soundtouch
|
||||
*
|
||||
* @author Ivaylo Ivanov - Initial contribution
|
||||
*/
|
||||
public class BoseSoundTouchConfiguration {
|
||||
|
||||
// Device configuration parameters;
|
||||
public static final String HOST = "host";
|
||||
public static final String MAC_ADDRESS = Thing.PROPERTY_MAC_ADDRESS;
|
||||
public static final String APP_KEY = "appKey";
|
||||
|
||||
public String host;
|
||||
public String macAddress;
|
||||
public String appKey;
|
||||
|
||||
// Not an actual configuration field, but it will contain the name of the group (in case of Stereo Pair)
|
||||
public String groupName;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
|
||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link BoseSoundTouchHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bosesoundtouch")
|
||||
public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private StorageService storageService;
|
||||
private BoseStateDescriptionOptionProvider stateOptionProvider;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
Storage<ContentItem> storage = storageService.getStorage(thing.getUID().toString(),
|
||||
ContentItem.class.getClassLoader());
|
||||
BoseSoundTouchHandler handler = new BoseSoundTouchHandler(thing, new PresetContainer(storage),
|
||||
stateOptionProvider);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setStorageService(StorageService storageService) {
|
||||
this.storageService = storageService;
|
||||
}
|
||||
|
||||
protected void unsetStorageService(StorageService storageService) {
|
||||
this.storageService = null;
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setPresetChannelTypeProvider(BoseStateDescriptionOptionProvider stateOptionProvider) {
|
||||
this.stateOptionProvider = stateOptionProvider;
|
||||
}
|
||||
|
||||
protected void unsetPresetChannelTypeProvider(BoseStateDescriptionOptionProvider stateOptionProvider) {
|
||||
this.stateOptionProvider = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link BoseSoundTouchNotFoundException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class BoseSoundTouchNotFoundException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BoseSoundTouchNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public BoseSoundTouchNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BoseSoundTouchNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BoseSoundTouchNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* Configuration class for soundtouch notification channel
|
||||
*
|
||||
* @author Ivaylo Ivanov - Initial contribution
|
||||
*/
|
||||
public class BoseSoundTouchNotificationChannelConfiguration {
|
||||
|
||||
public static final String MIN_FIRMWARE = "14";
|
||||
public static final String MODEL_TYPE = "sm2";
|
||||
|
||||
public static final String NOTIFICATION_VOLUME = "notificationVolume";
|
||||
public static final String NOTIFICATION_SERVICE = "notificationService";
|
||||
public static final String NOTIFICATION_REASON = "notificationReason";
|
||||
public static final String NOTIFICATION_MESSAGE = "notificationMessage";
|
||||
|
||||
public Integer notificationVolume;
|
||||
public String notificationService;
|
||||
public String notificationReason;
|
||||
public String notificationMessage;
|
||||
|
||||
public static boolean isSupportedFirmware(String firmware) {
|
||||
return firmware != null && firmware.compareTo(MIN_FIRMWARE) > 0;
|
||||
}
|
||||
|
||||
public static boolean isSupportedHardware(String hardware) {
|
||||
return MODEL_TYPE.equals(hardware);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of state options while leaving other state description fields as original.
|
||||
*
|
||||
* @author Ivaylo Ivanov - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, BoseStateDescriptionOptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class BoseStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CommandExecutor} class executes commands on the websocket
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
* @author Kai Kreuzer - code clean up
|
||||
*/
|
||||
public class CommandExecutor implements AvailableSources {
|
||||
private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
|
||||
|
||||
private final BoseSoundTouchHandler handler;
|
||||
|
||||
private boolean currentMuted;
|
||||
private ContentItem currentContentItem;
|
||||
private OperationModeType currentOperationMode;
|
||||
|
||||
private Map<String, Boolean> mapOfAvailableFunctions;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class
|
||||
*
|
||||
* @param handler the handler that created this CommandExecutor
|
||||
*/
|
||||
public CommandExecutor(BoseSoundTouchHandler handler) {
|
||||
this.handler = handler;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronizes the underlying storage container with the current value for the presets stored on the player
|
||||
* by updating the available ones and deleting the cleared ones
|
||||
*
|
||||
* @param playerPresets a Map<Integer, ContentItems> containing the items currently stored on the player
|
||||
*/
|
||||
public void updatePresetContainerFromPlayer(Map<Integer, ContentItem> playerPresets) {
|
||||
playerPresets.forEach((k, v) -> {
|
||||
try {
|
||||
if (v != null) {
|
||||
handler.getPresetContainer().put(k, v);
|
||||
} else {
|
||||
handler.getPresetContainer().remove(k);
|
||||
}
|
||||
} catch (ContentItemNotPresetableException e) {
|
||||
logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
|
||||
}
|
||||
});
|
||||
|
||||
handler.refreshPresetChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ContentItem to the PresetContainer
|
||||
*
|
||||
* @param id the id the ContentItem should be reached
|
||||
* @param contentItem the contentItem that should be saved as PRESET. Note that a eventually set presetID of the
|
||||
* ContentItem will be overwritten with id
|
||||
*/
|
||||
public void addContentItemToPresetContainer(int id, ContentItem contentItem) {
|
||||
contentItem.setPresetID(id);
|
||||
try {
|
||||
handler.getPresetContainer().put(id, contentItem);
|
||||
} catch (ContentItemNotPresetableException e) {
|
||||
logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
|
||||
}
|
||||
handler.refreshPresetChannel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current selected ContentItem to the PresetContainer
|
||||
*
|
||||
* @param command the command is a DecimalType, thats intValue will be used as id. The id the ContentItem should be
|
||||
* reached
|
||||
*/
|
||||
public void addCurrentContentItemToPresetContainer(DecimalType command) {
|
||||
if (command.intValue() > 6) {
|
||||
addContentItemToPresetContainer(command.intValue(), currentContentItem);
|
||||
} else {
|
||||
logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a API Request on this device
|
||||
*
|
||||
* @param apiRequest the apiRequest thats informations should be collected
|
||||
*/
|
||||
public void getInformations(APIRequest apiRequest) {
|
||||
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
|
||||
+ "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></msg>";
|
||||
handler.getSession().getRemote().sendStringByFuture(msg);
|
||||
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current ContentItem if it is valid, and inits an update of the operating values
|
||||
*
|
||||
* @param contentItem
|
||||
*/
|
||||
public void setCurrentContentItem(ContentItem contentItem) {
|
||||
if ((contentItem != null) && (contentItem.isValid())) {
|
||||
ContentItem psFound = null;
|
||||
if (handler.getPresetContainer() != null) {
|
||||
Collection<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
|
||||
for (ContentItem ps : listOfPresets) {
|
||||
if (ps.isPresetable()) {
|
||||
if (ps.getLocation().equals(contentItem.getLocation())) {
|
||||
psFound = ps;
|
||||
}
|
||||
}
|
||||
}
|
||||
int presetID = 0;
|
||||
if (psFound != null) {
|
||||
presetID = psFound.getPresetID();
|
||||
}
|
||||
contentItem.setPresetID(presetID);
|
||||
|
||||
currentContentItem = contentItem;
|
||||
}
|
||||
}
|
||||
updateOperatingValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the device is currently muted
|
||||
*
|
||||
* @param muted
|
||||
*/
|
||||
public void setCurrentMuted(boolean muted) {
|
||||
currentMuted = muted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post Bass on the device
|
||||
*
|
||||
* @param command the command is Type of DecimalType
|
||||
*/
|
||||
public void postBass(DecimalType command) {
|
||||
if (isBassAvailable()) {
|
||||
sendPostRequestInWebSocket("bass",
|
||||
"<bass deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</bass>");
|
||||
} else {
|
||||
logger.warn("{}: Bass modification not supported for this device", handler.getDeviceName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post OperationMode on the device
|
||||
*
|
||||
* @param command the command is Type of OperationModeType
|
||||
*/
|
||||
public void postOperationMode(OperationModeType command) {
|
||||
if (command == OperationModeType.STANDBY) {
|
||||
postPower(OnOffType.OFF);
|
||||
} else {
|
||||
try {
|
||||
ContentItemMaker contentItemMaker = new ContentItemMaker(this, handler.getPresetContainer());
|
||||
ContentItem contentItem = contentItemMaker.getContentItem(command);
|
||||
postContentItem(contentItem);
|
||||
} catch (OperationModeNotAvailableException e) {
|
||||
logger.warn("{}: OperationMode \"{}\" is not supported yet", handler.getDeviceName(),
|
||||
command.toString());
|
||||
} catch (NoInternetRadioPresetFoundException e) {
|
||||
logger.warn("{}: Unable to switch to mode \"INTERNET_RADIO\". No PRESET defined",
|
||||
handler.getDeviceName());
|
||||
} catch (NoStoredMusicPresetFoundException e) {
|
||||
logger.warn("{}: Unable to switch to mode: \"STORED_MUSIC\". No PRESET defined",
|
||||
handler.getDeviceName());
|
||||
}
|
||||
updateOperatingValues();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post PlayerControl on the device
|
||||
*
|
||||
* @param command the command is Type of Command
|
||||
*/
|
||||
public void postPlayerControl(Command command) {
|
||||
if (command.equals(PlayPauseType.PLAY)) {
|
||||
if (currentOperationMode == OperationModeType.STANDBY) {
|
||||
postRemoteKey(RemoteKeyType.POWER);
|
||||
} else {
|
||||
postRemoteKey(RemoteKeyType.PLAY);
|
||||
}
|
||||
} else if (command.equals(PlayPauseType.PAUSE)) {
|
||||
postRemoteKey(RemoteKeyType.PAUSE);
|
||||
} else if (command.equals(NextPreviousType.NEXT)) {
|
||||
postRemoteKey(RemoteKeyType.NEXT_TRACK);
|
||||
} else if (command.equals(NextPreviousType.PREVIOUS)) {
|
||||
postRemoteKey(RemoteKeyType.PREV_TRACK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post Power on the device
|
||||
*
|
||||
* @param command the command is Type of OnOffType
|
||||
*/
|
||||
public void postPower(OnOffType command) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
if (currentOperationMode == OperationModeType.STANDBY) {
|
||||
postRemoteKey(RemoteKeyType.POWER);
|
||||
}
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
if (currentOperationMode != OperationModeType.STANDBY) {
|
||||
postRemoteKey(RemoteKeyType.POWER);
|
||||
}
|
||||
}
|
||||
updateOperatingValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Post Preset on the device
|
||||
*
|
||||
* @param command the command is Type of DecimalType
|
||||
*/
|
||||
public void postPreset(DecimalType command) {
|
||||
ContentItem item = null;
|
||||
try {
|
||||
item = handler.getPresetContainer().get(command.intValue());
|
||||
postContentItem(item);
|
||||
} catch (NoPresetFoundException e) {
|
||||
logger.warn("{}: No preset found at id: {}", handler.getDeviceName(), command.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post RemoteKey on the device
|
||||
*
|
||||
* @param command the command is Type of RemoteKeyType
|
||||
*/
|
||||
public void postRemoteKey(RemoteKeyType key) {
|
||||
sendPostRequestInWebSocket("key", "mainNode=\"keyPress\"",
|
||||
"<key state=\"press\" sender=\"Gabbo\">" + key.name() + "</key>");
|
||||
sendPostRequestInWebSocket("key", "mainNode=\"keyRelease\"",
|
||||
"<key state=\"release\" sender=\"Gabbo\">" + key.name() + "</key>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Post Volume on the device
|
||||
*
|
||||
* @param command the command is Type of PercentType
|
||||
*/
|
||||
public void postVolume(PercentType command) {
|
||||
sendPostRequestInWebSocket("volume",
|
||||
"<volume deviceID=\"" + handler.getMacAddress() + "\"" + ">" + command.intValue() + "</volume>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Post VolumeMute on the device
|
||||
*
|
||||
* @param command the command is Type of OnOffType
|
||||
*/
|
||||
public void postVolumeMuted(OnOffType command) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
if (!currentMuted) {
|
||||
currentMuted = true;
|
||||
postRemoteKey(RemoteKeyType.MUTE);
|
||||
}
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
if (currentMuted) {
|
||||
currentMuted = false;
|
||||
postRemoteKey(RemoteKeyType.MUTE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for Basslevel
|
||||
*
|
||||
* @param state the state is Type of DecimalType
|
||||
*/
|
||||
public void updateBassLevelGUIState(DecimalType state) {
|
||||
handler.updateState(CHANNEL_BASS, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for Volume
|
||||
*
|
||||
* @param state the state is Type of PercentType
|
||||
*/
|
||||
public void updateVolumeGUIState(PercentType state) {
|
||||
handler.updateState(CHANNEL_VOLUME, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for OperationMode
|
||||
*
|
||||
* @param state the state is Type of StringType
|
||||
*/
|
||||
public void updateOperationModeGUIState(StringType state) {
|
||||
handler.updateState(CHANNEL_OPERATIONMODE, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for PlayerControl
|
||||
*
|
||||
* @param state the state is Type of State
|
||||
*/
|
||||
public void updatePlayerControlGUIState(State state) {
|
||||
handler.updateState(CHANNEL_PLAYER_CONTROL, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for Power
|
||||
*
|
||||
* @param state the state is Type of OnOffType
|
||||
*/
|
||||
public void updatePowerStateGUIState(OnOffType state) {
|
||||
handler.updateState(CHANNEL_POWER, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update GUI for Preset
|
||||
*
|
||||
* @param state the state is Type of DecimalType
|
||||
*/
|
||||
public void updatePresetGUIState(DecimalType state) {
|
||||
handler.updateState(CHANNEL_PRESET, state);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
getInformations(APIRequest.INFO);
|
||||
currentOperationMode = OperationModeType.OFFLINE;
|
||||
currentContentItem = null;
|
||||
|
||||
mapOfAvailableFunctions = new HashMap<>();
|
||||
}
|
||||
|
||||
private void postContentItem(ContentItem contentItem) {
|
||||
if (contentItem != null) {
|
||||
setCurrentContentItem(contentItem);
|
||||
sendPostRequestInWebSocket("select", "", contentItem.generateXML());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPostRequestInWebSocket(String url, String postData) {
|
||||
sendPostRequestInWebSocket(url, "", postData);
|
||||
}
|
||||
|
||||
private void sendPostRequestInWebSocket(String url, String infoAddon, String postData) {
|
||||
int id = 0;
|
||||
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
|
||||
+ "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
|
||||
+ " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
|
||||
try {
|
||||
handler.getSession().getRemote().sendStringByFuture(msg);
|
||||
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
||||
} catch (NullPointerException e) {
|
||||
handler.onWebSocketError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOperatingValues() {
|
||||
OperationModeType operationMode;
|
||||
if (currentContentItem != null) {
|
||||
updatePresetGUIState(new DecimalType(currentContentItem.getPresetID()));
|
||||
operationMode = currentContentItem.getOperationMode();
|
||||
} else {
|
||||
operationMode = OperationModeType.STANDBY;
|
||||
}
|
||||
|
||||
updateOperationModeGUIState(new StringType(operationMode.toString()));
|
||||
currentOperationMode = operationMode;
|
||||
if (currentOperationMode == OperationModeType.STANDBY) {
|
||||
updatePowerStateGUIState(OnOffType.OFF);
|
||||
updatePlayerControlGUIState(PlayPauseType.PAUSE);
|
||||
} else {
|
||||
updatePowerStateGUIState(OnOffType.ON);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBluetoothAvailable() {
|
||||
return isSourceAvailable("bluetooth");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAUXAvailable() {
|
||||
return isSourceAvailable("aux");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAUX1Available() {
|
||||
return isSourceAvailable("aux1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAUX2Available() {
|
||||
return isSourceAvailable("aux2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAUX3Available() {
|
||||
return isSourceAvailable("aux3");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTVAvailable() {
|
||||
return isSourceAvailable("tv");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHDMI1Available() {
|
||||
return isSourceAvailable("hdmi1");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInternetRadioAvailable() {
|
||||
return isSourceAvailable("internetRadio");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStoredMusicAvailable() {
|
||||
return isSourceAvailable("storedMusic");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBassAvailable() {
|
||||
return isSourceAvailable("bass");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBluetoothAvailable(boolean bluetooth) {
|
||||
mapOfAvailableFunctions.put("bluetooth", bluetooth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAUXAvailable(boolean aux) {
|
||||
mapOfAvailableFunctions.put("aux", aux);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAUX1Available(boolean aux1) {
|
||||
mapOfAvailableFunctions.put("aux1", aux1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAUX2Available(boolean aux2) {
|
||||
mapOfAvailableFunctions.put("aux2", aux2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAUX3Available(boolean aux3) {
|
||||
mapOfAvailableFunctions.put("aux3", aux3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStoredMusicAvailable(boolean storedMusic) {
|
||||
mapOfAvailableFunctions.put("storedMusic", storedMusic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInternetRadioAvailable(boolean internetRadio) {
|
||||
mapOfAvailableFunctions.put("internetRadio", internetRadio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTVAvailable(boolean tv) {
|
||||
mapOfAvailableFunctions.put("tv", tv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHDMI1Available(boolean hdmi1) {
|
||||
mapOfAvailableFunctions.put("hdmi1", hdmi1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBassAvailable(boolean bass) {
|
||||
mapOfAvailableFunctions.put("bass", bass);
|
||||
}
|
||||
|
||||
private boolean isSourceAvailable(String source) {
|
||||
Boolean isAvailable = mapOfAvailableFunctions.get(source);
|
||||
if (isAvailable == null) {
|
||||
return false;
|
||||
} else {
|
||||
return isAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
public void playNotificationSound(String appKey, BoseSoundTouchNotificationChannelConfiguration notificationConfig,
|
||||
String fileUrl) {
|
||||
String msg = "<play_info>" + "<app_key>" + appKey + "</app_key>" + "<url>" + fileUrl + "</url>" + "<service>"
|
||||
+ notificationConfig.notificationService + "</service>"
|
||||
+ (notificationConfig.notificationReason != null
|
||||
? "<reason>" + notificationConfig.notificationReason + "</reason>"
|
||||
: "")
|
||||
+ (notificationConfig.notificationMessage != null
|
||||
? "<message>" + notificationConfig.notificationMessage + "</message>"
|
||||
: "")
|
||||
+ (notificationConfig.notificationVolume != null
|
||||
? "<volume>" + notificationConfig.notificationVolume + "</volume>"
|
||||
: "")
|
||||
+ "</play_info>";
|
||||
|
||||
sendPostRequestInWebSocket("speaker", msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.openhab.core.types.StateOption;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
|
||||
/**
|
||||
* The {@link ContentItem} class manages a ContentItem
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class ContentItem {
|
||||
|
||||
private String source;
|
||||
private String sourceAccount;
|
||||
private String location;
|
||||
private boolean presetable;
|
||||
private String itemName;
|
||||
private int presetID;
|
||||
private String containerArt;
|
||||
@Expose
|
||||
private final Map<String, String> additionalAttributes;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class
|
||||
*/
|
||||
public ContentItem() {
|
||||
source = "";
|
||||
sourceAccount = null;
|
||||
location = null;
|
||||
presetable = false;
|
||||
itemName = null;
|
||||
presetID = 0;
|
||||
containerArt = null;
|
||||
additionalAttributes = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this ContentItem is defined as Preset
|
||||
*
|
||||
* @return true if this ContentItem is defined as Preset
|
||||
*/
|
||||
public boolean isPreset() {
|
||||
if (presetable) {
|
||||
return presetID > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all necessary stats are set
|
||||
*
|
||||
* @return true if all necessary stats are set
|
||||
*/
|
||||
public boolean isValid() {
|
||||
if (getOperationMode() == OperationModeType.STANDBY) {
|
||||
return true;
|
||||
}
|
||||
if (itemName == null || source == null || itemName.isEmpty() || source.isEmpty()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if source, sourceAccount, location, itemName, and presetable are equal
|
||||
*
|
||||
* @return true if source, sourceAccount, location, itemName, and presetable are equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ContentItem) {
|
||||
ContentItem other = (ContentItem) obj;
|
||||
if (!isEqual(other.source, this.source)) {
|
||||
return false;
|
||||
}
|
||||
if (!isEqual(other.sourceAccount, this.sourceAccount)) {
|
||||
return false;
|
||||
}
|
||||
if (other.presetable != this.presetable) {
|
||||
return false;
|
||||
}
|
||||
if (!isEqual(other.location, this.location)) {
|
||||
return false;
|
||||
}
|
||||
if (!isEqual(other.itemName, this.itemName)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operation Mode, depending on the stats that are set
|
||||
*
|
||||
* @return the operation Mode, depending on the stats that are set
|
||||
*/
|
||||
public OperationModeType getOperationMode() {
|
||||
OperationModeType operationMode = OperationModeType.OTHER;
|
||||
if (source == null || source.equals("")) {
|
||||
return OperationModeType.OTHER;
|
||||
}
|
||||
if (source.contains("PRODUCT")) {
|
||||
if (sourceAccount.contains("TV")) {
|
||||
operationMode = OperationModeType.TV;
|
||||
}
|
||||
if (sourceAccount.contains("HDMI")) {
|
||||
operationMode = OperationModeType.HDMI1;
|
||||
}
|
||||
return operationMode;
|
||||
}
|
||||
try {
|
||||
operationMode = OperationModeType.valueOf(source);
|
||||
return operationMode;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return OperationModeType.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public void setSourceAccount(String sourceAccount) {
|
||||
this.sourceAccount = sourceAccount;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public void setItemName(String itemName) {
|
||||
this.itemName = itemName;
|
||||
}
|
||||
|
||||
public void setAdditionalAttribute(String name, String value) {
|
||||
this.additionalAttributes.put(name, value);
|
||||
}
|
||||
|
||||
public void setPresetable(boolean presetable) {
|
||||
this.presetable = presetable;
|
||||
}
|
||||
|
||||
public void setPresetID(int presetID) {
|
||||
this.presetID = presetID;
|
||||
}
|
||||
|
||||
public void setContainerArt(String containerArt) {
|
||||
this.containerArt = containerArt;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public String getSourceAccount() {
|
||||
return sourceAccount;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public boolean isPresetable() {
|
||||
return presetable;
|
||||
}
|
||||
|
||||
public int getPresetID() {
|
||||
return presetID;
|
||||
}
|
||||
|
||||
public String getContainerArt() {
|
||||
return containerArt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML Code that is needed to switch to this ContentItem
|
||||
*
|
||||
* @return the XML Code that is needed to switch to this ContentItem
|
||||
*/
|
||||
public String generateXML() {
|
||||
String xml;
|
||||
switch (getOperationMode()) {
|
||||
case BLUETOOTH:
|
||||
xml = "<ContentItem source=\"BLUETOOTH\"></ContentItem>";
|
||||
break;
|
||||
case AUX:
|
||||
case AUX1:
|
||||
case AUX2:
|
||||
case AUX3:
|
||||
xml = "<ContentItem source=\"AUX\" sourceAccount=\"" + sourceAccount + "\"></ContentItem>";
|
||||
break;
|
||||
case TV:
|
||||
xml = "<ContentItem source=\"PRODUCT\" sourceAccount=\"TV\" isPresetable=\"false\" />";
|
||||
break;
|
||||
case HDMI1:
|
||||
xml = "<ContentItem source=\"PRODUCT\" sourceAccount=\"HDMI_1\" isPresetable=\"false\" />";
|
||||
break;
|
||||
default:
|
||||
StringBuilder sbXml = new StringBuilder("<ContentItem");
|
||||
if (source != null) {
|
||||
sbXml.append(" source=\"").append(StringEscapeUtils.escapeXml(source)).append("\"");
|
||||
}
|
||||
if (location != null) {
|
||||
sbXml.append(" location=\"").append(StringEscapeUtils.escapeXml(location)).append("\"");
|
||||
}
|
||||
if (sourceAccount != null) {
|
||||
sbXml.append(" sourceAccount=\"").append(StringEscapeUtils.escapeXml(sourceAccount)).append("\"");
|
||||
}
|
||||
sbXml.append(" isPresetable=\"").append(presetable).append("\"");
|
||||
for (Map.Entry<String, String> aae : additionalAttributes.entrySet()) {
|
||||
sbXml.append(" ").append(aae.getKey()).append("=\"")
|
||||
.append(StringEscapeUtils.escapeXml(aae.getValue())).append("\"");
|
||||
}
|
||||
sbXml.append(">");
|
||||
if (itemName != null) {
|
||||
sbXml.append("<itemName>").append(itemName).append("</itemName>");
|
||||
}
|
||||
if (containerArt != null) {
|
||||
sbXml.append("<containerArt>").append(containerArt).append("</containerArt>");
|
||||
}
|
||||
sbXml.append("</ContentItem>");
|
||||
xml = sbXml.toString();
|
||||
break;
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
public StateOption toStateOption() {
|
||||
String stateOptionLabel = String.valueOf(presetID) + ": " + itemName;
|
||||
return new StateOption(String.valueOf(presetID), stateOptionLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// if (presetID >= 1 && presetID <= 6) {
|
||||
// StringBuilder buffer = new StringBuilder();
|
||||
// buffer.append("PRESET_");
|
||||
// buffer.append(presetID);
|
||||
// return buffer.toString();
|
||||
// }
|
||||
return itemName;
|
||||
}
|
||||
|
||||
private boolean isEqual(String s1, String s2) {
|
||||
if (s1 == s2) {
|
||||
return true;
|
||||
}
|
||||
if (s1 == null || s2 == null) {
|
||||
return false;
|
||||
}
|
||||
return s1.equals(s2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* The {@link ContentItemMaker} class makes ContentItems for sources
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class ContentItemMaker {
|
||||
|
||||
private final PresetContainer presetContainer;
|
||||
private final CommandExecutor commandExecutor;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class
|
||||
*/
|
||||
public ContentItemMaker(CommandExecutor commandExecutor, PresetContainer presetContainer) {
|
||||
this.commandExecutor = commandExecutor;
|
||||
this.presetContainer = presetContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid ContentItem, to switch to
|
||||
*
|
||||
* @param operationModeType
|
||||
*
|
||||
* @throws OperationModeNotAvailableException if OperationMode is not supported yet or on this device
|
||||
* @throws NoInternetRadioPresetFoundException if OperationMode is INTERNET_RADIO and no PRESET is defined
|
||||
* @throws NoStoredMusicPresetFoundException if OperationMode is STORED_MUSIC and no PRESET is defined
|
||||
*/
|
||||
public ContentItem getContentItem(OperationModeType operationModeType) throws OperationModeNotAvailableException,
|
||||
NoInternetRadioPresetFoundException, NoStoredMusicPresetFoundException {
|
||||
switch (operationModeType) {
|
||||
case OFFLINE:
|
||||
case OTHER:
|
||||
case STANDBY:
|
||||
throw new OperationModeNotAvailableException();
|
||||
case AMAZON:
|
||||
return getAmazon();
|
||||
case AUX:
|
||||
return getAUX();
|
||||
case AUX1:
|
||||
return getAUX1();
|
||||
case AUX2:
|
||||
return getAUX2();
|
||||
case AUX3:
|
||||
return getAUX3();
|
||||
case BLUETOOTH:
|
||||
return getBluetooth();
|
||||
case DEEZER:
|
||||
return getDeezer();
|
||||
case HDMI1:
|
||||
return getHDMI();
|
||||
case INTERNET_RADIO:
|
||||
return getInternetRadio();
|
||||
case PANDORA:
|
||||
return getPandora();
|
||||
case SIRIUSXM:
|
||||
return getSiriusxm();
|
||||
case SPOTIFY:
|
||||
return getSpotify();
|
||||
case STORED_MUSIC:
|
||||
return getStoredMusic();
|
||||
case TV:
|
||||
return getTV();
|
||||
default:
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getAmazon() throws OperationModeNotAvailableException {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
|
||||
private ContentItem getAUX() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isAUXAvailable()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("AUX");
|
||||
contentItem.setSourceAccount("AUX");
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getAUX1() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isAUX1Available()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("AUX");
|
||||
contentItem.setSourceAccount("AUX1");
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getAUX2() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isAUX2Available()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("AUX");
|
||||
contentItem.setSourceAccount("AUX2");
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getAUX3() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isAUX3Available()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("AUX");
|
||||
contentItem.setSourceAccount("AUX3");
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getBluetooth() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isBluetoothAvailable()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("BLUETOOTH");
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getDeezer() throws OperationModeNotAvailableException {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
|
||||
private ContentItem getHDMI() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isHDMI1Available()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("PRODUCT");
|
||||
contentItem.setSourceAccount("HDMI_1");
|
||||
contentItem.setPresetable(false);
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getInternetRadio() throws NoInternetRadioPresetFoundException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isInternetRadioAvailable()) {
|
||||
Collection<ContentItem> listOfPresets = presetContainer.getAllPresets();
|
||||
for (ContentItem iteratedItem : listOfPresets) {
|
||||
if ((contentItem == null) && (iteratedItem.getOperationMode() == OperationModeType.INTERNET_RADIO)) {
|
||||
contentItem = iteratedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new NoInternetRadioPresetFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getPandora() throws OperationModeNotAvailableException {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
|
||||
private ContentItem getSiriusxm() throws OperationModeNotAvailableException {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
|
||||
private ContentItem getSpotify() throws OperationModeNotAvailableException {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
|
||||
private ContentItem getStoredMusic() throws NoStoredMusicPresetFoundException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isStoredMusicAvailable()) {
|
||||
Collection<ContentItem> listOfPresets = presetContainer.getAllPresets();
|
||||
for (ContentItem iteratedItem : listOfPresets) {
|
||||
if ((contentItem == null) && (iteratedItem.getOperationMode() == OperationModeType.STORED_MUSIC)) {
|
||||
contentItem = iteratedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new NoStoredMusicPresetFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private ContentItem getTV() throws OperationModeNotAvailableException {
|
||||
ContentItem contentItem = null;
|
||||
if (commandExecutor.isTVAvailable()) {
|
||||
contentItem = new ContentItem();
|
||||
contentItem.setSource("PRODUCT");
|
||||
contentItem.setSourceAccount("TV");
|
||||
contentItem.setPresetable(false);
|
||||
}
|
||||
if (contentItem != null) {
|
||||
return contentItem;
|
||||
} else {
|
||||
throw new OperationModeNotAvailableException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link ContentItemNotPresetableException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class ContentItemNotPresetableException extends NoPresetFoundException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ContentItemNotPresetableException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ContentItemNotPresetableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ContentItemNotPresetableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ContentItemNotPresetableException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link NoInternetRadioPresetFoundException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class NoInternetRadioPresetFoundException extends NoPresetFoundException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoInternetRadioPresetFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoInternetRadioPresetFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoInternetRadioPresetFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NoInternetRadioPresetFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link NoPresetFoundException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class NoPresetFoundException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoPresetFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoPresetFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoPresetFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NoPresetFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link NoStoredMusicPresetFoundException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class NoStoredMusicPresetFoundException extends NoPresetFoundException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public NoStoredMusicPresetFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoStoredMusicPresetFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoStoredMusicPresetFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NoStoredMusicPresetFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link OperationModeNotAvailableException} class is an exception
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class OperationModeNotAvailableException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public OperationModeNotAvailableException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OperationModeNotAvailableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public OperationModeNotAvailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public OperationModeNotAvailableException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link OperationModeType} class is holding all OperationModes
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public enum OperationModeType {
|
||||
OFFLINE,
|
||||
STANDBY,
|
||||
INTERNET_RADIO,
|
||||
BLUETOOTH,
|
||||
AUX,
|
||||
AUX1,
|
||||
AUX2,
|
||||
AUX3,
|
||||
SPOTIFY,
|
||||
PANDORA,
|
||||
DEEZER,
|
||||
SIRIUSXM,
|
||||
STORED_MUSIC,
|
||||
AMAZON,
|
||||
TV,
|
||||
HDMI1,
|
||||
TUNEIN,
|
||||
ALEXA,
|
||||
OTHER;
|
||||
|
||||
private String name;
|
||||
|
||||
private OperationModeType() {
|
||||
this.name = name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.core.storage.DeletableStorage;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link PresetContainer} class manages a PresetContainer which contains all additional Presets
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
* @author Kai Kreuzer - Refactored it to use storage instead of file
|
||||
*/
|
||||
public class PresetContainer {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PresetContainer.class);
|
||||
|
||||
private HashMap<Integer, ContentItem> mapOfPresets;
|
||||
private Storage<ContentItem> storage;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class
|
||||
*/
|
||||
public PresetContainer(Storage<ContentItem> storage) {
|
||||
this.storage = storage;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
this.mapOfPresets = new HashMap<>();
|
||||
readFromStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Collection of all Presets
|
||||
*
|
||||
* @param operationModeType
|
||||
*/
|
||||
public Collection<ContentItem> getAllPresets() {
|
||||
return mapOfPresets.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ContentItem as Preset, with presetID. Note that a eventually existing id in preset will be overwritten by
|
||||
* presetID
|
||||
*
|
||||
* @param presetID
|
||||
* @param preset
|
||||
*
|
||||
* @throws ContentItemNotPresetableException if ContentItem is not presetable
|
||||
*/
|
||||
public void put(int presetID, ContentItem preset) throws ContentItemNotPresetableException {
|
||||
preset.setPresetID(presetID);
|
||||
if (preset.isPresetable()) {
|
||||
mapOfPresets.put(presetID, preset);
|
||||
writeToStorage();
|
||||
} else {
|
||||
throw new ContentItemNotPresetableException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the Preset stored under the specified Id
|
||||
*
|
||||
* @param presetID
|
||||
*/
|
||||
public void remove(int presetID) {
|
||||
mapOfPresets.remove(presetID);
|
||||
writeToStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Preset with presetID
|
||||
*
|
||||
* @param presetID
|
||||
*
|
||||
* @throws NoPresetFoundException if Preset could not be found
|
||||
*/
|
||||
public ContentItem get(int presetID) throws NoPresetFoundException {
|
||||
ContentItem psFound = mapOfPresets.get(presetID);
|
||||
if (psFound != null) {
|
||||
return psFound;
|
||||
} else {
|
||||
throw new NoPresetFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all presets from the storage.
|
||||
*/
|
||||
public void clear() {
|
||||
if (storage instanceof DeletableStorage) {
|
||||
((DeletableStorage<ContentItem>) storage).delete();
|
||||
} else {
|
||||
Collection<@NonNull String> keys = storage.getKeys();
|
||||
keys.forEach(key -> storage.remove(key));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeToStorage() {
|
||||
Collection<ContentItem> colletionOfPresets = getAllPresets();
|
||||
List<ContentItem> listOfPresets = new ArrayList<>();
|
||||
listOfPresets.addAll(colletionOfPresets);
|
||||
// Only binding presets get saved
|
||||
for (Iterator<ContentItem> cii = listOfPresets.iterator(); cii.hasNext();) {
|
||||
if (cii.next().getPresetID() <= 6) {
|
||||
cii.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!listOfPresets.isEmpty()) {
|
||||
listOfPresets.forEach(item -> storage.put(String.valueOf(item.getPresetID()), item));
|
||||
}
|
||||
}
|
||||
|
||||
private void readFromStorage() {
|
||||
Collection<ContentItem> items = storage.getValues();
|
||||
for (ContentItem item : items) {
|
||||
try {
|
||||
put(item.getPresetID(), item);
|
||||
} catch (ContentItemNotPresetableException e) {
|
||||
logger.debug("Item '{}' is not presetable - ignoring it.", item.getItemName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link RemoteKeyType} class is holding the Keys on a remote. For simulating key presses
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
*/
|
||||
public enum RemoteKeyType {
|
||||
PLAY,
|
||||
PAUSE,
|
||||
STOP,
|
||||
PREV_TRACK,
|
||||
NEXT_TRACK,
|
||||
THUMBS_UP,
|
||||
THUMBS_DOWN,
|
||||
BOOKMARK,
|
||||
POWER,
|
||||
MUTE,
|
||||
VOLUME_UP,
|
||||
VOLUME_DOWN,
|
||||
PRESET_1,
|
||||
PRESET_2,
|
||||
PRESET_3,
|
||||
PRESET_4,
|
||||
PRESET_5,
|
||||
PRESET_6,
|
||||
AUX_INPUT,
|
||||
SHUFFLE_OFF,
|
||||
SHUFFLE_ON,
|
||||
REPEAT_OFF,
|
||||
REPEAT_ONE,
|
||||
REPEAT_ALL,
|
||||
PLAY_PAUSE,
|
||||
ADD_FAVORITE,
|
||||
REMOVE_FAVORITE,
|
||||
INVALID_KEY;
|
||||
|
||||
private String name;
|
||||
|
||||
private RemoteKeyType() {
|
||||
this.name = name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
/**
|
||||
* The {@link XMLHandlerState} class defines the XML States provided from Bose Soundtouch
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public enum XMLHandlerState {
|
||||
INIT,
|
||||
Msg,
|
||||
MsgHeader,
|
||||
MsgBody,
|
||||
Bass,
|
||||
BassActual,
|
||||
BassTarget,
|
||||
BassUpdated,
|
||||
BassMin,
|
||||
BassMax,
|
||||
BassDefault,
|
||||
ContentItem,
|
||||
ContentItemItemName,
|
||||
ContentItemContainerArt,
|
||||
Group,
|
||||
GroupName,
|
||||
Components,
|
||||
Component,
|
||||
Info,
|
||||
InfoName,
|
||||
InfoType,
|
||||
InfoModuleType,
|
||||
InfoFirmwareVersion,
|
||||
Presets,
|
||||
Preset,
|
||||
MasterDeviceId,
|
||||
DeviceId,
|
||||
DeviceIp,
|
||||
NowPlaying,
|
||||
NowPlayingAlbum,
|
||||
NowPlayingArt,
|
||||
NowPlayingArtist,
|
||||
NowPlayingDescription,
|
||||
NowPlayingGenre,
|
||||
NowPlayingPlayStatus,
|
||||
NowPlayingRateEnabled,
|
||||
NowPlayingSkipEnabled,
|
||||
NowPlayingSkipPreviousEnabled,
|
||||
NowPlayingStationLocation,
|
||||
NowPlayingStationName,
|
||||
NowPlayingTrack,
|
||||
Unprocessed, // unprocessed / ignored data
|
||||
UnprocessedNoTextExpected, // unprocessed / ignored data
|
||||
Updates,
|
||||
Volume,
|
||||
VolumeActual,
|
||||
VolumeTarget,
|
||||
VolumeUpdated,
|
||||
VolumeMuteEnabled,
|
||||
Zone,
|
||||
ZoneMember,
|
||||
ZoneUpdated,
|
||||
Sources,
|
||||
BassCapabilities,
|
||||
BassAvailable,
|
||||
}
|
||||
@@ -0,0 +1,690 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_HARDWARE_VERSION;
|
||||
import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
/**
|
||||
* The {@link XMLResponseHandler} class handles the XML communication with the Soundtouch
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
* @author Kai Kreuzer - code clean up
|
||||
*/
|
||||
public class XMLResponseHandler extends DefaultHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XMLResponseHandler.class);
|
||||
|
||||
private BoseSoundTouchHandler handler;
|
||||
private CommandExecutor commandExecutor;
|
||||
|
||||
private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
|
||||
|
||||
private Stack<XMLHandlerState> states;
|
||||
private XMLHandlerState state;
|
||||
private boolean msgHeaderWasValid;
|
||||
|
||||
private ContentItem contentItem;
|
||||
private boolean volumeMuteEnabled;
|
||||
private OnOffType rateEnabled;
|
||||
private OnOffType skipEnabled;
|
||||
private OnOffType skipPreviousEnabled;
|
||||
|
||||
private State nowPlayingSource;
|
||||
|
||||
private BoseSoundTouchConfiguration masterDeviceId;
|
||||
String deviceId;
|
||||
|
||||
private Map<Integer, ContentItem> playerPresets;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class
|
||||
*
|
||||
* @param handler
|
||||
* @param stateSwitchingMap the stateSwitchingMap is the XMLState Map, that says which Flags are computed
|
||||
*/
|
||||
public XMLResponseHandler(BoseSoundTouchHandler handler,
|
||||
Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap) {
|
||||
this.handler = handler;
|
||||
this.commandExecutor = handler.getCommandExecutor();
|
||||
this.stateSwitchingMap = stateSwitchingMap;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
||||
super.startElement(uri, localName, qName, attributes);
|
||||
logger.trace("{}: startElement('{}'; state: {})", handler.getDeviceName(), localName, state);
|
||||
states.push(state);
|
||||
XMLHandlerState curState = state; // save for switch statement
|
||||
Map<String, XMLHandlerState> stateMap = stateSwitchingMap.get(state);
|
||||
state = XMLHandlerState.Unprocessed; // set default value; we avoid default in select to have the compiler
|
||||
// showing a
|
||||
// warning for unhandled states
|
||||
switch (curState) {
|
||||
case INIT:
|
||||
if ("updates".equals(localName)) {
|
||||
// it just seems to be a ping - havn't seen any data on it..
|
||||
if (checkDeviceId(localName, attributes, false)) {
|
||||
state = XMLHandlerState.Updates;
|
||||
} else {
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
} else {
|
||||
state = stateMap.get(localName);
|
||||
if (state == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Msg:
|
||||
if ("header".equals(localName)) {
|
||||
// message
|
||||
if (checkDeviceId(localName, attributes, false)) {
|
||||
state = XMLHandlerState.MsgHeader;
|
||||
msgHeaderWasValid = true;
|
||||
} else {
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
} else if ("body".equals(localName)) {
|
||||
if (msgHeaderWasValid) {
|
||||
state = XMLHandlerState.MsgBody;
|
||||
} else {
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
break;
|
||||
case MsgHeader:
|
||||
if ("request".equals(localName)) {
|
||||
state = XMLHandlerState.Unprocessed; // TODO implement request id / response tracking...
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
break;
|
||||
case MsgBody:
|
||||
if ("nowPlaying".equals(localName)) {
|
||||
/*
|
||||
* if (!checkDeviceId(localName, attributes, true)) {
|
||||
* state = XMLHandlerState.Unprocessed;
|
||||
* break;
|
||||
* }
|
||||
*/
|
||||
rateEnabled = OnOffType.OFF;
|
||||
skipEnabled = OnOffType.OFF;
|
||||
skipPreviousEnabled = OnOffType.OFF;
|
||||
state = XMLHandlerState.NowPlaying;
|
||||
String source = attributes.getValue("source");
|
||||
if (nowPlayingSource == null || !nowPlayingSource.toString().equals(source)) {
|
||||
// source changed
|
||||
nowPlayingSource = new StringType(source);
|
||||
// reset enabled states
|
||||
updateRateEnabled(OnOffType.OFF);
|
||||
updateSkipEnabled(OnOffType.OFF);
|
||||
updateSkipPreviousEnabled(OnOffType.OFF);
|
||||
|
||||
// clear all "nowPlaying" details on source change...
|
||||
updateNowPlayingAlbum(UnDefType.NULL);
|
||||
updateNowPlayingArtwork(UnDefType.NULL);
|
||||
updateNowPlayingArtist(UnDefType.NULL);
|
||||
updateNowPlayingDescription(UnDefType.NULL);
|
||||
updateNowPlayingGenre(UnDefType.NULL);
|
||||
updateNowPlayingItemName(UnDefType.NULL);
|
||||
updateNowPlayingStationLocation(UnDefType.NULL);
|
||||
updateNowPlayingStationName(UnDefType.NULL);
|
||||
updateNowPlayingTrack(UnDefType.NULL);
|
||||
}
|
||||
} else if ("zone".equals(localName)) {
|
||||
state = XMLHandlerState.Zone;
|
||||
} else if ("presets".equals(localName)) {
|
||||
// reset the current playerPrests
|
||||
playerPresets = new HashMap<>();
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
playerPresets.put(i, null);
|
||||
}
|
||||
state = XMLHandlerState.Presets;
|
||||
} else if ("group".equals(localName)) {
|
||||
this.masterDeviceId = new BoseSoundTouchConfiguration();
|
||||
state = stateMap.get(localName);
|
||||
} else {
|
||||
state = stateMap.get(localName);
|
||||
if (state == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
} else if (state != XMLHandlerState.Volume && state != XMLHandlerState.Presets
|
||||
&& state != XMLHandlerState.Group && state != XMLHandlerState.Unprocessed) {
|
||||
if (!checkDeviceId(localName, attributes, false)) {
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Presets:
|
||||
if ("preset".equals(localName)) {
|
||||
state = XMLHandlerState.Preset;
|
||||
String id = attributes.getValue("id");
|
||||
if (contentItem == null) {
|
||||
contentItem = new ContentItem();
|
||||
}
|
||||
contentItem.setPresetID(Integer.parseInt(id));
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
break;
|
||||
case Sources:
|
||||
if ("sourceItem".equals(localName)) {
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
String source = attributes.getValue("source");
|
||||
String sourceAccount = attributes.getValue("sourceAccount");
|
||||
String status = attributes.getValue("status");
|
||||
if (status.equals("READY")) {
|
||||
if (source.equals("AUX")) {
|
||||
if (sourceAccount.equals("AUX")) {
|
||||
commandExecutor.setAUXAvailable(true);
|
||||
}
|
||||
if (sourceAccount.equals("AUX1")) {
|
||||
commandExecutor.setAUX1Available(true);
|
||||
}
|
||||
if (sourceAccount.equals("AUX2")) {
|
||||
commandExecutor.setAUX2Available(true);
|
||||
}
|
||||
if (sourceAccount.equals("AUX3")) {
|
||||
commandExecutor.setAUX3Available(true);
|
||||
}
|
||||
}
|
||||
if (source.equals("STORED_MUSIC")) {
|
||||
commandExecutor.setStoredMusicAvailable(true);
|
||||
}
|
||||
if (source.equals("INTERNET_RADIO")) {
|
||||
commandExecutor.setInternetRadioAvailable(true);
|
||||
}
|
||||
if (source.equals("BLUETOOTH")) {
|
||||
commandExecutor.setBluetoothAvailable(true);
|
||||
}
|
||||
if (source.equals("PRODUCT")) {
|
||||
if (sourceAccount.equals("TV")) {
|
||||
commandExecutor.setTVAvailable(true);
|
||||
}
|
||||
if (sourceAccount.equals("HDMI_1")) {
|
||||
commandExecutor.setHDMI1Available(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
break;
|
||||
// auto go trough the state map
|
||||
case Group:
|
||||
case Zone:
|
||||
case Bass:
|
||||
case ContentItem:
|
||||
case MasterDeviceId:
|
||||
case GroupName:
|
||||
case DeviceId:
|
||||
case DeviceIp:
|
||||
case Info:
|
||||
case NowPlaying:
|
||||
case Preset:
|
||||
case Updates:
|
||||
case Volume:
|
||||
case Components:
|
||||
case Component:
|
||||
state = nextState(stateMap, curState, localName);
|
||||
break;
|
||||
case BassCapabilities:
|
||||
state = nextState(stateMap, curState, localName);
|
||||
break;
|
||||
// all entities without any children expected..
|
||||
case BassTarget:
|
||||
case BassActual:
|
||||
case BassUpdated:
|
||||
case BassMin:
|
||||
case BassMax:
|
||||
case BassDefault:
|
||||
case ContentItemItemName:
|
||||
case ContentItemContainerArt:
|
||||
case InfoName:
|
||||
case InfoType:
|
||||
case InfoFirmwareVersion:
|
||||
case InfoModuleType:
|
||||
case NowPlayingAlbum:
|
||||
case NowPlayingArt:
|
||||
case NowPlayingArtist:
|
||||
case NowPlayingGenre:
|
||||
case NowPlayingDescription:
|
||||
case NowPlayingPlayStatus:
|
||||
case NowPlayingRateEnabled:
|
||||
case NowPlayingSkipEnabled:
|
||||
case NowPlayingSkipPreviousEnabled:
|
||||
case NowPlayingStationLocation:
|
||||
case NowPlayingStationName:
|
||||
case NowPlayingTrack:
|
||||
case VolumeTarget:
|
||||
case VolumeActual:
|
||||
case VolumeUpdated:
|
||||
case VolumeMuteEnabled:
|
||||
case ZoneMember:
|
||||
case ZoneUpdated: // currently this dosn't provide any zone details..
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
break;
|
||||
case BassAvailable:
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||
localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
break;
|
||||
case Unprocessed:
|
||||
// all further things are also unprocessed
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
break;
|
||||
case UnprocessedNoTextExpected:
|
||||
state = XMLHandlerState.UnprocessedNoTextExpected;
|
||||
break;
|
||||
}
|
||||
if (state == XMLHandlerState.ContentItem) {
|
||||
if (contentItem == null) {
|
||||
contentItem = new ContentItem();
|
||||
}
|
||||
contentItem.setSource(attributes.getValue("source"));
|
||||
contentItem.setSourceAccount(attributes.getValue("sourceAccount"));
|
||||
contentItem.setLocation(attributes.getValue("location"));
|
||||
contentItem.setPresetable(Boolean.parseBoolean(attributes.getValue("isPresetable")));
|
||||
for (int attrId = 0; attrId < attributes.getLength(); attrId++) {
|
||||
String attrName = attributes.getLocalName(attrId);
|
||||
if ("source".equalsIgnoreCase(attrName)) {
|
||||
continue;
|
||||
}
|
||||
if ("location".equalsIgnoreCase(attrName)) {
|
||||
continue;
|
||||
}
|
||||
if ("sourceAccount".equalsIgnoreCase(attrName)) {
|
||||
continue;
|
||||
}
|
||||
if ("isPresetable".equalsIgnoreCase(attrName)) {
|
||||
continue;
|
||||
}
|
||||
contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||
super.endElement(uri, localName, qName);
|
||||
logger.trace("{}: endElement('{}')", handler.getDeviceName(), localName);
|
||||
final XMLHandlerState prevState = state;
|
||||
state = states.pop();
|
||||
switch (prevState) {
|
||||
case Info:
|
||||
commandExecutor.getInformations(APIRequest.VOLUME);
|
||||
commandExecutor.getInformations(APIRequest.PRESETS);
|
||||
commandExecutor.getInformations(APIRequest.NOW_PLAYING);
|
||||
commandExecutor.getInformations(APIRequest.GET_ZONE);
|
||||
commandExecutor.getInformations(APIRequest.BASS);
|
||||
commandExecutor.getInformations(APIRequest.SOURCES);
|
||||
commandExecutor.getInformations(APIRequest.BASSCAPABILITIES);
|
||||
commandExecutor.getInformations(APIRequest.GET_GROUP);
|
||||
break;
|
||||
case ContentItem:
|
||||
if (state == XMLHandlerState.NowPlaying) {
|
||||
// update now playing name...
|
||||
updateNowPlayingItemName(new StringType(contentItem.getItemName()));
|
||||
commandExecutor.setCurrentContentItem(contentItem);
|
||||
}
|
||||
break;
|
||||
case Preset:
|
||||
if (state == XMLHandlerState.Presets) {
|
||||
playerPresets.put(contentItem.getPresetID(), contentItem);
|
||||
contentItem = null;
|
||||
}
|
||||
break;
|
||||
case NowPlaying:
|
||||
if (state == XMLHandlerState.MsgBody) {
|
||||
updateRateEnabled(rateEnabled);
|
||||
updateSkipEnabled(skipEnabled);
|
||||
updateSkipPreviousEnabled(skipPreviousEnabled);
|
||||
}
|
||||
break;
|
||||
// handle special tags..
|
||||
case BassUpdated:
|
||||
// request current bass level
|
||||
commandExecutor.getInformations(APIRequest.BASS);
|
||||
break;
|
||||
case VolumeUpdated:
|
||||
commandExecutor.getInformations(APIRequest.VOLUME);
|
||||
break;
|
||||
case NowPlayingRateEnabled:
|
||||
rateEnabled = OnOffType.ON;
|
||||
break;
|
||||
case NowPlayingSkipEnabled:
|
||||
skipEnabled = OnOffType.ON;
|
||||
break;
|
||||
case NowPlayingSkipPreviousEnabled:
|
||||
skipPreviousEnabled = OnOffType.ON;
|
||||
break;
|
||||
case Volume:
|
||||
OnOffType muted = volumeMuteEnabled ? OnOffType.ON : OnOffType.OFF;
|
||||
commandExecutor.setCurrentMuted(volumeMuteEnabled);
|
||||
commandExecutor.postVolumeMuted(muted);
|
||||
break;
|
||||
case ZoneUpdated:
|
||||
commandExecutor.getInformations(APIRequest.GET_ZONE);
|
||||
break;
|
||||
case Presets:
|
||||
commandExecutor.updatePresetContainerFromPlayer(playerPresets);
|
||||
playerPresets = null;
|
||||
break;
|
||||
case Group:
|
||||
handler.handleGroupUpdated(masterDeviceId);
|
||||
break;
|
||||
default:
|
||||
// no actions...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
logger.trace("{}: Text data during {}: '{}'", handler.getDeviceName(), state, new String(ch, start, length));
|
||||
super.characters(ch, start, length);
|
||||
switch (state) {
|
||||
case INIT:
|
||||
case Msg:
|
||||
case MsgHeader:
|
||||
case MsgBody:
|
||||
case Bass:
|
||||
case BassUpdated:
|
||||
case Updates:
|
||||
case Volume:
|
||||
case VolumeUpdated:
|
||||
case Info:
|
||||
case Preset:
|
||||
case Presets:
|
||||
case NowPlaying:
|
||||
case NowPlayingRateEnabled:
|
||||
case NowPlayingSkipEnabled:
|
||||
case NowPlayingSkipPreviousEnabled:
|
||||
case ContentItem:
|
||||
case UnprocessedNoTextExpected:
|
||||
case Zone:
|
||||
case ZoneUpdated:
|
||||
case Sources:
|
||||
logger.debug("{}: Unexpected text data during {}: '{}'", handler.getDeviceName(), state,
|
||||
new String(ch, start, length));
|
||||
break;
|
||||
case BassMin: // @TODO - find out how to dynamically change "channel-type" bass configuration
|
||||
case BassMax: // based on these values...
|
||||
case BassDefault:
|
||||
case BassTarget:
|
||||
case VolumeTarget:
|
||||
// this are currently unprocessed values.
|
||||
break;
|
||||
case BassCapabilities:
|
||||
logger.debug("{}: Unexpected text data during {}: '{}'", handler.getDeviceName(), state,
|
||||
new String(ch, start, length));
|
||||
break;
|
||||
case Unprocessed:
|
||||
// drop quietly..
|
||||
break;
|
||||
case BassActual:
|
||||
commandExecutor.updateBassLevelGUIState(new DecimalType(new String(ch, start, length)));
|
||||
break;
|
||||
case InfoName:
|
||||
setConfigOption(DEVICE_INFO_NAME, new String(ch, start, length));
|
||||
break;
|
||||
case InfoType:
|
||||
setConfigOption(DEVICE_INFO_TYPE, new String(ch, start, length));
|
||||
setConfigOption(PROPERTY_MODEL_ID, new String(ch, start, length));
|
||||
break;
|
||||
case InfoModuleType:
|
||||
setConfigOption(PROPERTY_HARDWARE_VERSION, new String(ch, start, length));
|
||||
break;
|
||||
case InfoFirmwareVersion:
|
||||
String[] fwVersion = new String(ch, start, length).split(" ");
|
||||
setConfigOption(PROPERTY_FIRMWARE_VERSION, fwVersion[0]);
|
||||
break;
|
||||
case BassAvailable:
|
||||
boolean bassAvailable = Boolean.parseBoolean(new String(ch, start, length));
|
||||
commandExecutor.setBassAvailable(bassAvailable);
|
||||
break;
|
||||
case NowPlayingAlbum:
|
||||
updateNowPlayingAlbum(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case NowPlayingArt:
|
||||
String url = new String(ch, start, length);
|
||||
if (url.startsWith("http")) {
|
||||
// We download the cover art in a different thread to not delay the other operations
|
||||
handler.getScheduler().submit(() -> {
|
||||
RawType image = HttpUtil.downloadImage(url, true, 500000);
|
||||
if (image != null) {
|
||||
updateNowPlayingArtwork(image);
|
||||
} else {
|
||||
updateNowPlayingArtwork(UnDefType.UNDEF);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
updateNowPlayingArtwork(UnDefType.UNDEF);
|
||||
}
|
||||
break;
|
||||
case NowPlayingArtist:
|
||||
updateNowPlayingArtist(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case ContentItemItemName:
|
||||
contentItem.setItemName(new String(ch, start, length));
|
||||
break;
|
||||
case ContentItemContainerArt:
|
||||
contentItem.setContainerArt(new String(ch, start, length));
|
||||
break;
|
||||
case NowPlayingDescription:
|
||||
updateNowPlayingDescription(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case NowPlayingGenre:
|
||||
updateNowPlayingGenre(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case NowPlayingPlayStatus:
|
||||
String playPauseState = new String(ch, start, length);
|
||||
if ("PLAY_STATE".equals(playPauseState) || "BUFFERING_STATE".equals(playPauseState)) {
|
||||
commandExecutor.updatePlayerControlGUIState(PlayPauseType.PLAY);
|
||||
} else if ("STOP_STATE".equals(playPauseState) || "PAUSE_STATE".equals(playPauseState)) {
|
||||
commandExecutor.updatePlayerControlGUIState(PlayPauseType.PAUSE);
|
||||
}
|
||||
break;
|
||||
case NowPlayingStationLocation:
|
||||
updateNowPlayingStationLocation(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case NowPlayingStationName:
|
||||
updateNowPlayingStationName(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case NowPlayingTrack:
|
||||
updateNowPlayingTrack(new StringType(new String(ch, start, length)));
|
||||
break;
|
||||
case VolumeActual:
|
||||
commandExecutor.updateVolumeGUIState(new PercentType(Integer.parseInt(new String(ch, start, length))));
|
||||
break;
|
||||
case VolumeMuteEnabled:
|
||||
volumeMuteEnabled = Boolean.parseBoolean(new String(ch, start, length));
|
||||
commandExecutor.setCurrentMuted(volumeMuteEnabled);
|
||||
break;
|
||||
case MasterDeviceId:
|
||||
if (masterDeviceId != null) {
|
||||
masterDeviceId.macAddress = new String(ch, start, length);
|
||||
}
|
||||
break;
|
||||
case GroupName:
|
||||
if (masterDeviceId != null) {
|
||||
masterDeviceId.groupName = new String(ch, start, length);
|
||||
}
|
||||
break;
|
||||
case DeviceId:
|
||||
deviceId = new String(ch, start, length);
|
||||
break;
|
||||
case DeviceIp:
|
||||
if (masterDeviceId != null && Objects.equals(masterDeviceId.macAddress, deviceId)) {
|
||||
masterDeviceId.host = new String(ch, start, length);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skippedEntity(String name) throws SAXException {
|
||||
super.skippedEntity(name);
|
||||
}
|
||||
|
||||
private boolean checkDeviceId(String localName, Attributes attributes, boolean allowFromMaster) {
|
||||
String deviceID = attributes.getValue("deviceID");
|
||||
if (deviceID == null) {
|
||||
logger.warn("{}: No device-ID in entity {}", handler.getDeviceName(), localName);
|
||||
return false;
|
||||
}
|
||||
if (deviceID.equals(handler.getMacAddress())) {
|
||||
return true;
|
||||
}
|
||||
logger.warn("{}: Wrong device-ID in entity '{}': Got: '{}', expected: '{}'", handler.getDeviceName(), localName,
|
||||
deviceID, handler.getMacAddress());
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
states = new Stack<>();
|
||||
state = XMLHandlerState.INIT;
|
||||
nowPlayingSource = null;
|
||||
}
|
||||
|
||||
private XMLHandlerState nextState(Map<String, XMLHandlerState> stateMap, XMLHandlerState curState,
|
||||
String localName) {
|
||||
XMLHandlerState state = stateMap.get(localName);
|
||||
if (state == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState, localName);
|
||||
}
|
||||
state = XMLHandlerState.Unprocessed;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private void setConfigOption(String option, String value) {
|
||||
Map<String, String> prop = handler.getThing().getProperties();
|
||||
String cur = prop.get(option);
|
||||
if (cur == null || !cur.equals(value)) {
|
||||
logger.debug("{}: Option '{}' updated: From '{}' to '{}'", handler.getDeviceName(), option, cur, value);
|
||||
handler.getThing().setProperty(option, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNowPlayingAlbum(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_ALBUM, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingArtwork(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_ARTWORK, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingArtist(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_ARTIST, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingDescription(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_DESCRIPTION, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingGenre(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_GENRE, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingItemName(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_ITEMNAME, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingStationLocation(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_STATIONLOCATION, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingStationName(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_STATIONNAME, state);
|
||||
}
|
||||
|
||||
private void updateNowPlayingTrack(State state) {
|
||||
handler.updateState(CHANNEL_NOWPLAYING_TRACK, state);
|
||||
}
|
||||
|
||||
private void updateRateEnabled(OnOffType state) {
|
||||
handler.updateState(CHANNEL_RATEENABLED, state);
|
||||
}
|
||||
|
||||
private void updateSkipEnabled(OnOffType state) {
|
||||
handler.updateState(CHANNEL_SKIPENABLED, state);
|
||||
}
|
||||
|
||||
private void updateSkipPreviousEnabled(OnOffType state) {
|
||||
handler.updateState(CHANNEL_SKIPPREVIOUSENABLED, state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
/**
|
||||
* The {@link XMLResponseProcessor} class handles the XML mapping
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class XMLResponseProcessor {
|
||||
private BoseSoundTouchHandler handler;
|
||||
|
||||
private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
|
||||
|
||||
public XMLResponseProcessor(BoseSoundTouchHandler handler) {
|
||||
this.handler = handler;
|
||||
init();
|
||||
}
|
||||
|
||||
public void handleMessage(String msg) throws SAXException, IOException {
|
||||
XMLReader reader = XMLReaderFactory.createXMLReader();
|
||||
reader.setContentHandler(new XMLResponseHandler(handler, stateSwitchingMap));
|
||||
reader.parse(new InputSource(new StringReader(msg)));
|
||||
}
|
||||
|
||||
// initializes our XML parsing state machine
|
||||
private void init() {
|
||||
stateSwitchingMap = new HashMap<>();
|
||||
|
||||
Map<String, XMLHandlerState> msgInitMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.INIT, msgInitMap);
|
||||
msgInitMap.put("msg", XMLHandlerState.Msg);
|
||||
msgInitMap.put("SoundTouchSdkInfo", XMLHandlerState.Unprocessed);
|
||||
msgInitMap.put("userActivityUpdate", XMLHandlerState.Unprocessed); // ignored..
|
||||
|
||||
Map<String, XMLHandlerState> msgBodyMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.MsgBody, msgBodyMap);
|
||||
msgBodyMap.put("info", XMLHandlerState.Info);
|
||||
msgBodyMap.put("volume", XMLHandlerState.Volume);
|
||||
msgBodyMap.put("presets", XMLHandlerState.Presets);
|
||||
msgBodyMap.put("key", XMLHandlerState.Unprocessed); // only confirmation of our key presses...
|
||||
msgBodyMap.put("status", XMLHandlerState.Unprocessed); // only confirmation of commands sent to device...
|
||||
msgBodyMap.put("zone", XMLHandlerState.Zone); // only confirmation of our key presses...
|
||||
msgBodyMap.put("bass", XMLHandlerState.Bass);
|
||||
msgBodyMap.put("sources", XMLHandlerState.Sources);
|
||||
msgBodyMap.put("bassCapabilities", XMLHandlerState.BassCapabilities);
|
||||
msgBodyMap.put("group", XMLHandlerState.Group);
|
||||
|
||||
// info message states
|
||||
Map<String, XMLHandlerState> infoMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Info, infoMap);
|
||||
infoMap.put("components", XMLHandlerState.Info);
|
||||
infoMap.put("component", XMLHandlerState.Info);
|
||||
infoMap.put("name", XMLHandlerState.InfoName);
|
||||
infoMap.put("type", XMLHandlerState.InfoType);
|
||||
infoMap.put("componentCategory", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("softwareVersion", XMLHandlerState.InfoFirmwareVersion);
|
||||
infoMap.put("serialNumber", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("networkInfo", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("margeAccountUUID", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("margeURL", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("moduleType", XMLHandlerState.InfoModuleType);
|
||||
infoMap.put("variant", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("variantMode", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("countryCode", XMLHandlerState.Unprocessed);
|
||||
infoMap.put("regionCode", XMLHandlerState.Unprocessed);
|
||||
|
||||
Map<String, XMLHandlerState> updatesMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Updates, updatesMap);
|
||||
updatesMap.put("clockDisplayUpdated", XMLHandlerState.Unprocessed); // can we get anything useful of that?
|
||||
updatesMap.put("connectionStateUpdated", XMLHandlerState.UnprocessedNoTextExpected);
|
||||
updatesMap.put("infoUpdated", XMLHandlerState.Unprocessed);
|
||||
updatesMap.put("nowPlayingUpdated", XMLHandlerState.MsgBody);
|
||||
updatesMap.put("nowSelectionUpdated", XMLHandlerState.Unprocessed); // TODO this seems to be quite a useful info
|
||||
// what is currently played..
|
||||
updatesMap.put("recentsUpdated", XMLHandlerState.Unprocessed);
|
||||
updatesMap.put("volumeUpdated", XMLHandlerState.MsgBody);
|
||||
updatesMap.put("zoneUpdated", XMLHandlerState.ZoneUpdated); // just notifies but dosn't provide details
|
||||
updatesMap.put("bassUpdated", XMLHandlerState.BassUpdated);
|
||||
updatesMap.put("presetsUpdated", XMLHandlerState.MsgBody);
|
||||
updatesMap.put("groupUpdated", XMLHandlerState.MsgBody);
|
||||
|
||||
Map<String, XMLHandlerState> volume = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Volume, volume);
|
||||
volume.put("targetvolume", XMLHandlerState.VolumeTarget);
|
||||
volume.put("actualvolume", XMLHandlerState.VolumeActual);
|
||||
volume.put("muteenabled", XMLHandlerState.VolumeMuteEnabled);
|
||||
|
||||
Map<String, XMLHandlerState> nowPlayingMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.NowPlaying, nowPlayingMap);
|
||||
nowPlayingMap.put("album", XMLHandlerState.NowPlayingAlbum);
|
||||
nowPlayingMap.put("art", XMLHandlerState.NowPlayingArt);
|
||||
nowPlayingMap.put("artist", XMLHandlerState.NowPlayingArtist);
|
||||
nowPlayingMap.put("ContentItem", XMLHandlerState.ContentItem);
|
||||
nowPlayingMap.put("description", XMLHandlerState.NowPlayingDescription);
|
||||
nowPlayingMap.put("playStatus", XMLHandlerState.NowPlayingPlayStatus);
|
||||
nowPlayingMap.put("rateEnabled", XMLHandlerState.NowPlayingRateEnabled);
|
||||
nowPlayingMap.put("skipEnabled", XMLHandlerState.NowPlayingSkipEnabled);
|
||||
nowPlayingMap.put("skipPreviousEnabled", XMLHandlerState.NowPlayingSkipPreviousEnabled);
|
||||
nowPlayingMap.put("stationLocation", XMLHandlerState.NowPlayingStationLocation);
|
||||
nowPlayingMap.put("stationName", XMLHandlerState.NowPlayingStationName);
|
||||
nowPlayingMap.put("track", XMLHandlerState.NowPlayingTrack);
|
||||
nowPlayingMap.put("connectionStatusInfo", XMLHandlerState.Unprocessed); // TODO active when Source==Bluetooth
|
||||
// TODO active when Source==Pandora and maybe also other sources - seems to be rating related
|
||||
nowPlayingMap.put("time", XMLHandlerState.Unprocessed);
|
||||
nowPlayingMap.put("rating", XMLHandlerState.Unprocessed);
|
||||
nowPlayingMap.put("rateEnabled", XMLHandlerState.Unprocessed);
|
||||
|
||||
// ContentItem specifies a resource (that also could be bookmarked in a preset)
|
||||
Map<String, XMLHandlerState> contentItemMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.ContentItem, contentItemMap);
|
||||
contentItemMap.put("itemName", XMLHandlerState.ContentItemItemName);
|
||||
contentItemMap.put("containerArt", XMLHandlerState.ContentItemContainerArt);
|
||||
|
||||
Map<String, XMLHandlerState> presetMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Preset, presetMap);
|
||||
presetMap.put("ContentItem", XMLHandlerState.ContentItem);
|
||||
|
||||
Map<String, XMLHandlerState> zoneMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Zone, zoneMap);
|
||||
zoneMap.put("member", XMLHandlerState.ZoneMember);
|
||||
|
||||
Map<String, XMLHandlerState> bassMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Bass, bassMap);
|
||||
bassMap.put("targetbass", XMLHandlerState.BassTarget);
|
||||
bassMap.put("actualbass", XMLHandlerState.BassActual);
|
||||
|
||||
Map<String, XMLHandlerState> sourceMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Sources, sourceMap);
|
||||
|
||||
Map<String, XMLHandlerState> bassCapabilitiesMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.BassCapabilities, bassCapabilitiesMap);
|
||||
bassCapabilitiesMap.put("bassAvailable", XMLHandlerState.BassAvailable);
|
||||
bassCapabilitiesMap.put("bassMin", XMLHandlerState.BassMin);
|
||||
bassCapabilitiesMap.put("bassMax", XMLHandlerState.BassMax);
|
||||
bassCapabilitiesMap.put("bassDefault", XMLHandlerState.BassDefault);
|
||||
|
||||
Map<String, XMLHandlerState> groupsMap = new HashMap<>();
|
||||
stateSwitchingMap.put(XMLHandlerState.Group, groupsMap);
|
||||
groupsMap.put("name", XMLHandlerState.GroupName);
|
||||
groupsMap.put("masterDeviceId", XMLHandlerState.MasterDeviceId);
|
||||
groupsMap.put("roles", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("senderIPAddress", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("status", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("roles", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("groupRole", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("deviceId", XMLHandlerState.DeviceId);
|
||||
groupsMap.put("role", XMLHandlerState.Unprocessed);
|
||||
groupsMap.put("ipAddress", XMLHandlerState.DeviceIp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal.discovery;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
|
||||
/**
|
||||
* The {@link DiscoveryUtil} is a static helper class, to get infos from a XML
|
||||
*
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
public class DiscoveryUtil {
|
||||
|
||||
/**
|
||||
* Finds the content in an element
|
||||
*
|
||||
* This is a quick and dirty method, it always delivers the first appearance of content in an element
|
||||
*/
|
||||
public static String getContentOfFirstElement(String content, String element) {
|
||||
if (content == null) {
|
||||
return "";
|
||||
}
|
||||
String beginTag = "<" + element + ">";
|
||||
String endTag = "</" + element + ">";
|
||||
|
||||
int startIndex = content.indexOf(beginTag) + beginTag.length();
|
||||
int endIndex = content.indexOf(endTag);
|
||||
|
||||
if (startIndex != -1 && endIndex != -1) {
|
||||
return content.substring(startIndex, endIndex);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an URL and returns to answer
|
||||
*/
|
||||
public static String executeUrl(String url) throws IOException {
|
||||
return HttpUtil.executeUrl("GET", url, 5000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SoundTouchDiscoveryParticipant} is responsible processing the
|
||||
* results of searches for mDNS services of type _soundtouch._tcp.local.
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
*/
|
||||
@Component(immediate = true, configurationPid = "discovery.bosesoundtouch")
|
||||
public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SoundTouchDiscoveryParticipant.class);
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscoveryResult createResult(ServiceInfo info) {
|
||||
DiscoveryResult result = null;
|
||||
ThingUID uid = getThingUID(info);
|
||||
if (uid != null) {
|
||||
// remove the domain from the name
|
||||
InetAddress[] addrs = info.getInetAddresses();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
|
||||
String label = null;
|
||||
if (BST_10_THING_TYPE_UID.equals(getThingTypeUID(info))) {
|
||||
try {
|
||||
String group = DiscoveryUtil.executeUrl("http://" + addrs[0].getHostAddress() + ":8090/getGroup");
|
||||
label = DiscoveryUtil.getContentOfFirstElement(group, "name");
|
||||
} catch (IOException e) {
|
||||
logger.debug("Can't obtain label for group. Will use the default one");
|
||||
}
|
||||
}
|
||||
|
||||
if (label == null || label.isEmpty()) {
|
||||
label = info.getName();
|
||||
}
|
||||
|
||||
if (label == null || label.isEmpty()) {
|
||||
label = "Bose SoundTouch";
|
||||
}
|
||||
|
||||
// we expect only one address per device..
|
||||
if (addrs.length > 1) {
|
||||
logger.warn("Bose SoundTouch device {} ({}) reports multiple addresses - using the first one: {}",
|
||||
info.getName(), label, Arrays.toString(addrs));
|
||||
}
|
||||
|
||||
properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress());
|
||||
if (getMacAddress(info) != null) {
|
||||
properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS,
|
||||
new String(getMacAddress(info), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// Set manufacturer as thing property (if available)
|
||||
byte[] manufacturer = info.getPropertyBytes("MANUFACTURER");
|
||||
if (manufacturer != null) {
|
||||
properties.put(Thing.PROPERTY_VENDOR, new String(manufacturer, StandardCharsets.UTF_8));
|
||||
}
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).withTTL(600).build();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getThingUID(ServiceInfo info) {
|
||||
logger.trace("ServiceInfo: {}", info);
|
||||
ThingTypeUID typeUID = getThingTypeUID(info);
|
||||
if (typeUID != null) {
|
||||
if (info.getType() != null) {
|
||||
if (info.getType().equals(getServiceType())) {
|
||||
logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName());
|
||||
byte[] mac = getMacAddress(info);
|
||||
if (mac != null) {
|
||||
return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return "_soundtouch._tcp.local.";
|
||||
}
|
||||
|
||||
private ThingTypeUID getThingTypeUID(ServiceInfo info) {
|
||||
InetAddress[] addrs = info.getInetAddresses();
|
||||
if (addrs.length > 0) {
|
||||
String ip = addrs[0].getHostAddress();
|
||||
String deviceId = null;
|
||||
byte[] mac = getMacAddress(info);
|
||||
if (mac != null) {
|
||||
deviceId = new String(mac, StandardCharsets.UTF_8);
|
||||
}
|
||||
String deviceType;
|
||||
try {
|
||||
String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
|
||||
deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (deviceType.toLowerCase().contains("soundtouch 10")) {
|
||||
// Check if it's a Stereo Pair
|
||||
try {
|
||||
String group = DiscoveryUtil.executeUrl("http://" + ip + ":8090/getGroup");
|
||||
String masterDevice = DiscoveryUtil.getContentOfFirstElement(group, "masterDeviceId");
|
||||
|
||||
if (Objects.equals(deviceId, masterDevice)) {
|
||||
// Stereo Pair - Master Device
|
||||
return BST_10_THING_TYPE_UID;
|
||||
} else if (!masterDevice.isEmpty()) {
|
||||
// Stereo Pair - Secondary Device - should not be paired
|
||||
return null;
|
||||
} else {
|
||||
// Single player
|
||||
return BST_10_THING_TYPE_UID;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("soundtouch 20")) {
|
||||
return BST_20_THING_TYPE_UID;
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("soundtouch 300")) {
|
||||
return BST_300_THING_TYPE_UID;
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("soundtouch 30")) {
|
||||
return BST_30_THING_TYPE_UID;
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("soundtouch wireless link adapter")) {
|
||||
return BST_WLA_THING_TYPE_UID;
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("wave")) {
|
||||
return BST_WSMS_THING_TYPE_UID;
|
||||
}
|
||||
if (deviceType.toLowerCase().contains("amplifier")) {
|
||||
return BST_SA5A_THING_TYPE_UID;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] getMacAddress(ServiceInfo info) {
|
||||
if (info != null) {
|
||||
// sometimes we see empty messages - ignore them
|
||||
if (!info.hasData()) {
|
||||
return null;
|
||||
}
|
||||
byte[] mac = info.getPropertyBytes("MAC");
|
||||
if (mac == null) {
|
||||
logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
|
||||
return null;
|
||||
}
|
||||
if (mac.length != 12) {
|
||||
BigInteger bi = new BigInteger(1, mac);
|
||||
logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(),
|
||||
String.format("%0" + (mac.length << 1) + "X", bi));
|
||||
return null;
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,569 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.bosesoundtouch.internal.handler;
|
||||
|
||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.openhab.binding.bosesoundtouch.internal.APIRequest;
|
||||
import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
|
||||
import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchNotificationChannelConfiguration;
|
||||
import org.openhab.binding.bosesoundtouch.internal.BoseStateDescriptionOptionProvider;
|
||||
import org.openhab.binding.bosesoundtouch.internal.CommandExecutor;
|
||||
import org.openhab.binding.bosesoundtouch.internal.OperationModeType;
|
||||
import org.openhab.binding.bosesoundtouch.internal.PresetContainer;
|
||||
import org.openhab.binding.bosesoundtouch.internal.RemoteKeyType;
|
||||
import org.openhab.binding.bosesoundtouch.internal.XMLResponseProcessor;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link BoseSoundTouchHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Christian Niessner - Initial contribution
|
||||
* @author Thomas Traunbauer - Initial contribution
|
||||
* @author Kai Kreuzer - code clean up
|
||||
* @author Alexander Kostadinov - Handling of websocket ping-pong mechanism for thing status check
|
||||
*/
|
||||
public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocketListener, WebSocketFrameListener {
|
||||
|
||||
private static final int MAX_MISSED_PONGS_COUNT = 2;
|
||||
|
||||
private static final int RETRY_INTERVAL_IN_SECS = 30;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BoseSoundTouchHandler.class);
|
||||
|
||||
private ScheduledFuture<?> connectionChecker;
|
||||
private WebSocketClient client;
|
||||
private volatile Session session;
|
||||
private volatile CommandExecutor commandExecutor;
|
||||
private volatile int missedPongsCount = 0;
|
||||
|
||||
private XMLResponseProcessor xmlResponseProcessor;
|
||||
|
||||
private PresetContainer presetContainer;
|
||||
private BoseStateDescriptionOptionProvider stateOptionProvider;
|
||||
|
||||
private Future<?> sessionFuture;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this class for the {@link Thing}.
|
||||
*
|
||||
* @param thing the thing that should be handled, not null
|
||||
* @param presetContainer the preset container instance to use for managing presets
|
||||
*
|
||||
* @throws IllegalArgumentException if thing or factory argument is null
|
||||
*/
|
||||
public BoseSoundTouchHandler(Thing thing, PresetContainer presetContainer,
|
||||
BoseStateDescriptionOptionProvider stateOptionProvider) {
|
||||
super(thing);
|
||||
this.presetContainer = presetContainer;
|
||||
this.stateOptionProvider = stateOptionProvider;
|
||||
xmlResponseProcessor = new XMLResponseProcessor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
connectionChecker = scheduler.scheduleWithFixedDelay(() -> checkConnection(), 0, RETRY_INTERVAL_IN_SECS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (connectionChecker != null && !connectionChecker.isCancelled()) {
|
||||
connectionChecker.cancel(true);
|
||||
connectionChecker = null;
|
||||
}
|
||||
closeConnection();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
presetContainer.clear();
|
||||
super.handleRemoval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(String channelID, State state) {
|
||||
// don't update channel if it's not linked (in case of Stereo Pair slave device)
|
||||
if (isLinked(channelID)) {
|
||||
super.updateState(channelID, state);
|
||||
} else {
|
||||
logger.debug("{}: Skipping state update because of not linked channel '{}'", getDeviceName(), channelID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (commandExecutor == null) {
|
||||
logger.debug("{}: Can't handle command '{}' for channel '{}' because of not initialized connection.",
|
||||
getDeviceName(), command, channelUID);
|
||||
return;
|
||||
} else {
|
||||
logger.debug("{}: handleCommand({}, {});", getDeviceName(), channelUID, command);
|
||||
}
|
||||
|
||||
if (command.equals(RefreshType.REFRESH)) {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case CHANNEL_BASS:
|
||||
commandExecutor.getInformations(APIRequest.BASS);
|
||||
break;
|
||||
case CHANNEL_KEY_CODE:
|
||||
// refresh makes no sense... ?
|
||||
break;
|
||||
case CHANNEL_NOWPLAYING_ALBUM:
|
||||
case CHANNEL_NOWPLAYING_ARTIST:
|
||||
case CHANNEL_NOWPLAYING_ARTWORK:
|
||||
case CHANNEL_NOWPLAYING_DESCRIPTION:
|
||||
case CHANNEL_NOWPLAYING_GENRE:
|
||||
case CHANNEL_NOWPLAYING_ITEMNAME:
|
||||
case CHANNEL_NOWPLAYING_STATIONLOCATION:
|
||||
case CHANNEL_NOWPLAYING_STATIONNAME:
|
||||
case CHANNEL_NOWPLAYING_TRACK:
|
||||
case CHANNEL_RATEENABLED:
|
||||
case CHANNEL_SKIPENABLED:
|
||||
case CHANNEL_SKIPPREVIOUSENABLED:
|
||||
commandExecutor.getInformations(APIRequest.NOW_PLAYING);
|
||||
break;
|
||||
case CHANNEL_VOLUME:
|
||||
commandExecutor.getInformations(APIRequest.VOLUME);
|
||||
break;
|
||||
default:
|
||||
logger.debug("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
|
||||
channelUID.getId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case CHANNEL_POWER:
|
||||
if (command instanceof OnOffType) {
|
||||
commandExecutor.postPower((OnOffType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_VOLUME:
|
||||
if (command instanceof PercentType) {
|
||||
commandExecutor.postVolume((PercentType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_MUTE:
|
||||
if (command instanceof OnOffType) {
|
||||
commandExecutor.postVolumeMuted((OnOffType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_OPERATIONMODE:
|
||||
if (command instanceof StringType) {
|
||||
String cmd = command.toString().toUpperCase().trim();
|
||||
try {
|
||||
OperationModeType mode = OperationModeType.valueOf(cmd);
|
||||
commandExecutor.postOperationMode(mode);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn("{}: OperationMode \"{}\" is not valid!", getDeviceName(), cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_PLAYER_CONTROL:
|
||||
if ((command instanceof PlayPauseType) || (command instanceof NextPreviousType)) {
|
||||
commandExecutor.postPlayerControl(command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_PRESET:
|
||||
if (command instanceof DecimalType) {
|
||||
commandExecutor.postPreset((DecimalType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_BASS:
|
||||
if (command instanceof DecimalType) {
|
||||
commandExecutor.postBass((DecimalType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_SAVE_AS_PRESET:
|
||||
if (command instanceof DecimalType) {
|
||||
commandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command);
|
||||
} else {
|
||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||
}
|
||||
break;
|
||||
case CHANNEL_KEY_CODE:
|
||||
if (command instanceof StringType) {
|
||||
String cmd = command.toString().toUpperCase().trim();
|
||||
try {
|
||||
RemoteKeyType keyCommand = RemoteKeyType.valueOf(cmd);
|
||||
commandExecutor.postRemoteKey(keyCommand);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug("{}: Unhandled remote key: {}", getDeviceName(), cmd);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Channel channel = getThing().getChannel(channelUID.getId());
|
||||
if (channel != null) {
|
||||
ChannelTypeUID chTypeUid = channel.getChannelTypeUID();
|
||||
if (chTypeUid != null) {
|
||||
switch (channel.getChannelTypeUID().getId()) {
|
||||
case CHANNEL_NOTIFICATION_SOUND:
|
||||
String appKey = Objects.toString(getConfig().get(BoseSoundTouchConfiguration.APP_KEY),
|
||||
null);
|
||||
if (appKey != null && !appKey.isEmpty()) {
|
||||
if (command instanceof StringType) {
|
||||
String url = command.toString();
|
||||
BoseSoundTouchNotificationChannelConfiguration notificationConfiguration = channel
|
||||
.getConfiguration()
|
||||
.as(BoseSoundTouchNotificationChannelConfiguration.class);
|
||||
if (!url.isEmpty()) {
|
||||
commandExecutor.playNotificationSound(appKey, notificationConfiguration,
|
||||
url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("Missing app key - cannot use notification api");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.warn("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
|
||||
channelUID.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CommandExecutor of this handler
|
||||
*
|
||||
* @return the CommandExecutor of this handler
|
||||
*/
|
||||
public CommandExecutor getCommandExecutor() {
|
||||
return commandExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Session this handler has opened
|
||||
*
|
||||
* @return the Session this handler has opened
|
||||
*/
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the device delivered from itself
|
||||
*
|
||||
* @return the name of the device delivered from itself
|
||||
*/
|
||||
public String getDeviceName() {
|
||||
return getThing().getProperties().get(DEVICE_INFO_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the device delivered from itself
|
||||
*
|
||||
* @return the type of the device delivered from itself
|
||||
*/
|
||||
public String getDeviceType() {
|
||||
return getThing().getProperties().get(DEVICE_INFO_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MAC Address of this device
|
||||
*
|
||||
* @return the MAC Address of this device (in format "123456789ABC")
|
||||
*/
|
||||
public String getMacAddress() {
|
||||
return ((String) getThing().getConfiguration().get(BoseSoundTouchConfiguration.MAC_ADDRESS)).replaceAll(":",
|
||||
"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IP Address of this device
|
||||
*
|
||||
* @return the IP Address of this device
|
||||
*/
|
||||
public String getIPAddress() {
|
||||
return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the handler internal scheduler instance
|
||||
*
|
||||
* @return the {@link ScheduledExecutorService} instance used by this handler
|
||||
*/
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public PresetContainer getPresetContainer() {
|
||||
return this.presetContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session) {
|
||||
logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session);
|
||||
this.session = session;
|
||||
commandExecutor = new CommandExecutor(this);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable e) {
|
||||
logger.debug("{}: Error during websocket communication: {}", getDeviceName(), e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
if (commandExecutor != null) {
|
||||
commandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
||||
commandExecutor = null;
|
||||
}
|
||||
if (session != null) {
|
||||
session.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + e.getMessage());
|
||||
session = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String msg) {
|
||||
logger.debug("{}: onWebSocketText('{}')", getDeviceName(), msg);
|
||||
try {
|
||||
xmlResponseProcessor.handleMessage(msg);
|
||||
} catch (Exception e) {
|
||||
logger.warn("{}: Could not parse XML from string '{}'.", getDeviceName(), msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] arr, int pos, int len) {
|
||||
// we don't expect binary data so just dump if we get some...
|
||||
logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), pos, len, Arrays.toString(arr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int code, String reason) {
|
||||
logger.debug("{}: onClose({}, '{}')", getDeviceName(), code, reason);
|
||||
missedPongsCount = 0;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
|
||||
if (commandExecutor != null) {
|
||||
commandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketFrame(Frame frame) {
|
||||
if (frame.getType() == Type.PONG) {
|
||||
missedPongsCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void openConnection() {
|
||||
closeConnection();
|
||||
try {
|
||||
client = new WebSocketClient();
|
||||
// we need longer timeouts for web socket.
|
||||
client.setMaxIdleTimeout(360 * 1000);
|
||||
// Port seems to be hard coded, therefore no user input or discovery is necessary
|
||||
String wsUrl = "ws://" + getIPAddress() + ":8080/";
|
||||
logger.debug("{}: Connecting to: {}", getDeviceName(), wsUrl);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
request.setSubProtocols("gabbo");
|
||||
client.setStopTimeout(1000);
|
||||
client.start();
|
||||
sessionFuture = client.connect(this, new URI(wsUrl), request);
|
||||
} catch (Exception e) {
|
||||
onWebSocketError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void closeConnection() {
|
||||
if (session != null) {
|
||||
try {
|
||||
session.close(StatusCode.NORMAL, "Binding shutdown");
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
||||
e.getClass().getName(), e.getMessage());
|
||||
}
|
||||
session = null;
|
||||
}
|
||||
if (sessionFuture != null && !sessionFuture.isDone()) {
|
||||
sessionFuture.cancel(true);
|
||||
}
|
||||
if (client != null) {
|
||||
try {
|
||||
client.stop();
|
||||
client.destroy();
|
||||
} catch (Exception e) {
|
||||
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
||||
e.getClass().getName(), e.getMessage());
|
||||
}
|
||||
client = null;
|
||||
}
|
||||
|
||||
commandExecutor = null;
|
||||
}
|
||||
|
||||
private void checkConnection() {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE || session == null || client == null
|
||||
|| commandExecutor == null) {
|
||||
openConnection(); // try to reconnect....
|
||||
}
|
||||
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE && this.session != null && this.session.isOpen()) {
|
||||
try {
|
||||
this.session.getRemote().sendPing(null);
|
||||
missedPongsCount++;
|
||||
} catch (IOException | NullPointerException e) {
|
||||
onWebSocketError(e);
|
||||
closeConnection();
|
||||
openConnection();
|
||||
}
|
||||
|
||||
if (missedPongsCount >= MAX_MISSED_PONGS_COUNT) {
|
||||
logger.debug("{}: Closing connection because of too many missed PONGs: {} (max allowed {}) ",
|
||||
getDeviceName(), missedPongsCount, MAX_MISSED_PONGS_COUNT);
|
||||
missedPongsCount = 0;
|
||||
closeConnection();
|
||||
openConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshPresetChannel() {
|
||||
List<StateOption> stateOptions = presetContainer.getAllPresets().stream().map(e -> e.toStateOption())
|
||||
.sorted(Comparator.comparing(StateOption::getValue)).collect(Collectors.toList());
|
||||
stateOptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PRESET), stateOptions);
|
||||
}
|
||||
|
||||
public void handleGroupUpdated(BoseSoundTouchConfiguration masterPlayerConfiguration) {
|
||||
String deviceId = getMacAddress();
|
||||
|
||||
if (masterPlayerConfiguration != null && masterPlayerConfiguration.macAddress != null) {
|
||||
// Stereo pair
|
||||
if (Objects.equals(masterPlayerConfiguration.macAddress, deviceId)) {
|
||||
if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
|
||||
logger.debug("{}: Stereo Pair was created and this is the master device.", getDeviceName());
|
||||
} else {
|
||||
logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
|
||||
getThing().getThingTypeUID());
|
||||
}
|
||||
} else {
|
||||
if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
|
||||
logger.debug("{}: Stereo Pair was created and this is NOT the master device.", getDeviceName());
|
||||
updateThing(editThing().withChannels(Collections.emptyList()).build());
|
||||
} else {
|
||||
logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
|
||||
getThing().getThingTypeUID());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// NO Stereo Pair
|
||||
if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
|
||||
if (getThing().getChannels().isEmpty()) {
|
||||
logger.debug("{}: Stereo Pair was disbounded. Restoring channels", getDeviceName());
|
||||
updateThing(editThing().withChannels(getAllChannels(BST_10_THING_TYPE_UID)).build());
|
||||
} else {
|
||||
logger.debug("{}: Stereo Pair was disbounded.", getDeviceName());
|
||||
}
|
||||
} else {
|
||||
logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
|
||||
getThing().getThingTypeUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Channel> getAllChannels(ThingTypeUID thingTypeUID) {
|
||||
ThingHandlerCallback callback = getCallback();
|
||||
if (callback == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return CHANNEL_IDS.stream()
|
||||
.map(channelId -> callback.createChannelBuilder(new ChannelUID(getThing().getUID(), channelId),
|
||||
createChannelTypeUID(thingTypeUID, channelId)).build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ChannelTypeUID createChannelTypeUID(ThingTypeUID thingTypeUID, String channelId) {
|
||||
if (CHANNEL_OPERATIONMODE.equals(channelId)) {
|
||||
return createOperationModeChannelTypeUID(thingTypeUID);
|
||||
}
|
||||
|
||||
return new ChannelTypeUID(BINDING_ID, channelId);
|
||||
}
|
||||
|
||||
private ChannelTypeUID createOperationModeChannelTypeUID(ThingTypeUID thingTypeUID) {
|
||||
String channelTypeId = CHANNEL_TYPE_OPERATION_MODE_DEFAULT;
|
||||
|
||||
if (BST_10_THING_TYPE_UID.equals(thingTypeUID) || BST_20_THING_TYPE_UID.equals(thingTypeUID)
|
||||
|| BST_30_THING_TYPE_UID.equals(thingTypeUID)) {
|
||||
channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_10_20_30;
|
||||
} else if (BST_300_THING_TYPE_UID.equals(thingTypeUID)) {
|
||||
channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_300;
|
||||
} else if (BST_SA5A_THING_TYPE_UID.equals(thingTypeUID)) {
|
||||
channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_SA5A;
|
||||
} else if (BST_WLA_THING_TYPE_UID.equals(thingTypeUID)) {
|
||||
channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_WLA;
|
||||
} else if (BST_WSMS_THING_TYPE_UID.equals(thingTypeUID)) {
|
||||
channelTypeId = CHANNEL_TYPE_OPERATION_MODE_DEFAULT;
|
||||
}
|
||||
|
||||
return new ChannelTypeUID(BINDING_ID, channelTypeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="bosesoundtouch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Bose SoundTouch Binding</name>
|
||||
<description>This is the binding for Bose SoundTouch devices.</description>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
<config-description uri="thing-type:bosesoundtouch:config">
|
||||
<parameter name="host" type="text" required="true">
|
||||
<label>Host Address</label>
|
||||
<description>The Host / IP Address used for communication to this device.
|
||||
</description>
|
||||
</parameter>
|
||||
<parameter name="macAddress" type="text" required="true">
|
||||
<label>MAC Address</label>
|
||||
<description>The MAC Address used for communication to this device.
|
||||
</description>
|
||||
</parameter>
|
||||
<parameter name="appKey" type="text" required="false">
|
||||
<label>Authorization Key</label>
|
||||
<description>An authorization key used to identify the client application. Should be requested from the developer
|
||||
portal.
|
||||
</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="channel-type:bosesoundtouch:notificationSound">
|
||||
<parameter name="notificationVolume" type="integer" min="10" max="70" step="1" unit="%" required="false">
|
||||
<label>Notification Sound Volume</label>
|
||||
<description>This indicates the desired volume level while playing the notification. The value represents a
|
||||
percentage (0 to 100) of the full audible range of the speaker device. A value less than 10 or greater than 70 will
|
||||
result in an error and not play the notification. Upon completion of the notification, the speaker volume will
|
||||
return to its original value. If not present, the notification will play at the existing volume level.</description>
|
||||
</parameter>
|
||||
<parameter name="notificationService" type="text" required="true">
|
||||
<label>Notification Service</label>
|
||||
<description>This indicates the service providing the notification. This text will appear on the device display (when
|
||||
available) and the SoundTouch application screen.
|
||||
</description>
|
||||
<default>Notification</default>
|
||||
</parameter>
|
||||
<parameter name="notificationReason" type="text" required="false">
|
||||
<label>Notification Reason</label>
|
||||
<description>This indicates the reason for the notification. This text will appear on the device display (when
|
||||
available) and the SoundTouch application screen. If a reason string is not provided, the field with be blank.
|
||||
</description>
|
||||
</parameter>
|
||||
<parameter name="notificationMessage" type="text" required="false">
|
||||
<label>Notification Message</label>
|
||||
<description>This indicates further details about the notification. This text will appear on the device display (when
|
||||
available) and the SoundTouch application screen. If a message string is not provided, the field with be blank.
|
||||
</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="10" extensible="notificationsound">
|
||||
<label>Bose SoundTouch 10</label>
|
||||
<description>Bose SoundTouch 10 Speaker</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_10_20_30"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
<!-- Notification channels -->
|
||||
<channel id="notificationsound" typeId="notificationsound"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="20" extensible="notificationsound">
|
||||
<label>Bose SoundTouch 20</label>
|
||||
<description>Bose SoundTouch 20 Speaker</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_10_20_30"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
<!-- Notification channels -->
|
||||
<channel id="notificationsound" typeId="notificationsound"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="30" extensible="notificationsound">
|
||||
<label>Bose SoundTouch 30</label>
|
||||
<description>Bose SoundTouch 30 Speaker</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_10_20_30"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
<!-- Notification channels -->
|
||||
<channel id="notificationsound" typeId="notificationsound"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="300">
|
||||
<label>Bose SoundTouch 300</label>
|
||||
<description>Bose SoundTouch 300 Speaker</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_300"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sa5Amplifier">
|
||||
<label>Bose SoundTouch SA-5 Amplifier</label>
|
||||
<description>A Bose SoundTouch SA-5 Amplifier</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_SA5_Amplifier"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="wirelessLinkAdapter">
|
||||
<label>Bose SoundTouch Wireless Link Adapter</label>
|
||||
<description>Bose SoundTouch Wireless Link Adapter</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_BST_WLA"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="device">
|
||||
<label>Bose SoundTouch Device</label>
|
||||
<description>Aan unknown Bose SoundTouch Device</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_default"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="waveSoundTouchMusicSystemIV">
|
||||
<label>Bose Wave SoundTouch Music System IV</label>
|
||||
<description>A Bose Wave SoundTouch Music System IV</description>
|
||||
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="bass" typeId="bass"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="operationMode" typeId="operationMode_default"/>
|
||||
<channel id="playerControl" typeId="playerControl"/>
|
||||
<channel id="preset" typeId="preset"/>
|
||||
<channel id="saveAsPreset" typeId="saveAsPreset"/>
|
||||
<channel id="keyCode" typeId="keyCode"/>
|
||||
<channel id="rateEnabled" typeId="rateEnabled"/>
|
||||
<channel id="skipEnabled" typeId="skipEnabled"/>
|
||||
<channel id="skipPreviousEnabled" typeId="skipPreviousEnabled"/>
|
||||
<channel id="nowPlayingAlbum" typeId="nowPlayingAlbum"/>
|
||||
<channel id="nowPlayingArtist" typeId="nowPlayingArtist"/>
|
||||
<channel id="nowPlayingArtwork" typeId="nowPlayingArtwork"/>
|
||||
<channel id="nowPlayingDescription" typeId="nowPlayingDescription"/>
|
||||
<channel id="nowPlayingGenre" typeId="nowPlayingGenre"/>
|
||||
<channel id="nowPlayingItemName" typeId="nowPlayingItemName"/>
|
||||
<channel id="nowPlayingStationLocation" typeId="nowPlayingStationLocation"/>
|
||||
<channel id="nowPlayingStationName" typeId="nowPlayingStationName"/>
|
||||
<channel id="nowPlayingTrack" typeId="nowPlayingTrack"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:bosesoundtouch:config"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,292 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="bosesoundtouch"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Channels common for all BOSE SOUNDTOUCH devices -->
|
||||
<channel-type id="keyCode" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Remote Key Code</label>
|
||||
<description>Simulates pushing a remote control button</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="PLAY">Play</option>
|
||||
<option value="PAUSE">Pause</option>
|
||||
<option value="STOP">Stop</option>
|
||||
<option value="PREV_TRACK">Prev Track</option>
|
||||
<option value="NEXT_TRACK">Next Track</option>
|
||||
<option value="THUMBS_UP">Thumbs Up</option>
|
||||
<option value="THUMBS_DOWN">Thumbs Down</option>
|
||||
<option value="BOOKMARK">Bookmark</option>
|
||||
<option value="POWER">Power</option>
|
||||
<option value="MUTE">Mute</option>
|
||||
<option value="VOLUME_UP">Volume Up</option>
|
||||
<option value="VOLUME_DOWN">Volume Down</option>
|
||||
<option value="PRESET_1">Preset 1</option>
|
||||
<option value="PRESET_2">Preset 2</option>
|
||||
<option value="PRESET_3">Preset 3</option>
|
||||
<option value="PRESET_4">Preset 4</option>
|
||||
<option value="PRESET_5">Preset 5</option>
|
||||
<option value="PRESET_6">Preset 6</option>
|
||||
<option value="AUX_INPUT">AUX Input</option>
|
||||
<option value="SHUFFLE_OFF">Shuffle Off</option>
|
||||
<option value="SHUFFLE_ON">Shuffle On</option>
|
||||
<option value="REPEAT_OFF">Repeat Off</option>
|
||||
<option value="REPEAT_ONE">Repeat One</option>
|
||||
<option value="REPEAT_ALL">Repeat All</option>
|
||||
<option value="PLAY_PAUSE">Play/Pause</option>
|
||||
<option value="ADD_FAVORITE">Add Favorite</option>
|
||||
<option value="REMOVE_FAVORITE">Remove Favorite</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mute">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mute</label>
|
||||
<description>Mutes the sound</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingAlbum" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Album</label>
|
||||
<description>Current playing album name</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingArtist" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Artist</label>
|
||||
<description>Current playing artist name</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingArtwork">
|
||||
<item-type>Image</item-type>
|
||||
<label>Artwork</label>
|
||||
<description>Artwork for the current playing song</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingDescription" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Description</label>
|
||||
<description>Description to current playing song</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingGenre" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Genre</label>
|
||||
<description>Genre of current playing song</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingItemName">
|
||||
<item-type>String</item-type>
|
||||
<label>Now Playing</label>
|
||||
<description>Visible description shown in display</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingStationLocation" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Station Location</label>
|
||||
<description>Location of current playing radio station</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingStationName" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Station Name</label>
|
||||
<description>Name of current playing radio station</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nowPlayingTrack" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Track</label>
|
||||
<description>Track currently playing</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="operationMode_default">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Current Operation Mode</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="INTERNET_RADIO">Internet Radio</option>
|
||||
<option value="BLUETOOTH">Bluetooth</option>
|
||||
<option value="STORED_MUSIC">Stored Music</option>
|
||||
<option value="AUX">AUX</option>
|
||||
<option value="AUX1">AUX1</option>
|
||||
<option value="AUX2">AUX2</option>
|
||||
<option value="AUX3">AUX3</option>
|
||||
<option value="TV">TV</option>
|
||||
<option value="HDMI">HDMI</option>
|
||||
<option value="SPOTIFY">Spotify</option>
|
||||
<option value="PANDORA">Pandora</option>
|
||||
<option value="DEEZER">Deezer</option>
|
||||
<option value="SIRIUSXM">SiriusXM</option>
|
||||
<option value="AMAZON">Amazon</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="playerControl">
|
||||
<item-type>Player</item-type>
|
||||
<label>Player Control</label>
|
||||
<description>Control the Player</description>
|
||||
<category>Player</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="power">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power</label>
|
||||
<description>SoundTouch power state</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="preset">
|
||||
<item-type>Number</item-type>
|
||||
<label>Preset</label>
|
||||
<description>1-6 Preset of Soundtouch, >7 Binding Presets</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rateEnabled" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Rating Enabled</label>
|
||||
<description>Current source allows rating</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="saveAsPreset" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Save as Preset</label>
|
||||
<description>A selected presetable Contentitem is save as Preset with number >6</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="skipEnabled" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Skip Enabled</label>
|
||||
<description>Current source allows skipping to next track</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="skipPreviousEnabled" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Skip/Previous Enabled</label>
|
||||
<description>Current source allows scrolling through tracks</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="volume">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Set or get the volume</description>
|
||||
<category>SoundVolume</category>
|
||||
</channel-type>
|
||||
|
||||
<!-- Channels common for BOSE SOUNDTOUCH 10/20/30 devices -->
|
||||
<channel-type id="bass" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Bass</label>
|
||||
<description>Bass (-9 minimum, 0 maximum)</description>
|
||||
<state min="-9" max="0" step="1" pattern="%d" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="operationMode_BST_10_20_30">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Current Operation Mode</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="INTERNET_RADIO">Internet Radio</option>
|
||||
<option value="BLUETOOTH">Bluetooth</option>
|
||||
<option value="STORED_MUSIC">Stored Music</option>
|
||||
<option value="AUX">AUX</option>
|
||||
<option value="SPOTIFY">Spotify</option>
|
||||
<option value="PANDORA">Pandora</option>
|
||||
<option value="DEEZER">Deezer</option>
|
||||
<option value="SIRIUSXM">SiriusXM</option>
|
||||
<option value="AMAZON">Amazon</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<!-- Channels common for BOSE SOUNDTOUCH 300 devices -->
|
||||
<channel-type id="operationMode_BST_300">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Current Operation Mode</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="INTERNET_RADIO">Internet Radio</option>
|
||||
<option value="BLUETOOTH">Bluetooth</option>
|
||||
<option value="STORED_MUSIC">Stored Music</option>
|
||||
<option value="TV">TV</option>
|
||||
<option value="HDMI">HDMI</option>
|
||||
<option value="SPOTIFY">Spotify</option>
|
||||
<option value="PANDORA">Pandora</option>
|
||||
<option value="DEEZER">Deezer</option>
|
||||
<option value="SIRIUSXM">SiriusXM</option>
|
||||
<option value="AMAZON">Amazon</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<!-- Channels common for BOSE SOUNDTOUCH Wireless Link Adapter devices -->
|
||||
<channel-type id="operationMode_BST_WLA">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Current Operation Mode</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="INTERNET_RADIO">Internet Radio</option>
|
||||
<option value="BLUETOOTH">Bluetooth</option>
|
||||
<option value="STORED_MUSIC">Stored Music</option>
|
||||
<option value="AUX">AUX</option>
|
||||
<option value="SPOTIFY">Spotify</option>
|
||||
<option value="PANDORA">Pandora</option>
|
||||
<option value="DEEZER">Deezer</option>
|
||||
<option value="SIRIUSXM">SiriusXM</option>
|
||||
<option value="AMAZON">Amazon</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<!-- Channels common for BOSE SOUNDTOUCH SA-5 Amplifier devices -->
|
||||
<channel-type id="operationMode_BST_SA5_Amplifier">
|
||||
<item-type>String</item-type>
|
||||
<label>Operation Mode</label>
|
||||
<description>Bose SoundTouch current Operation Mode</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="STANDBY">Standby</option>
|
||||
<option value="INTERNET_RADIO">Internet Radio</option>
|
||||
<option value="BLUETOOTH">Bluetooth</option>
|
||||
<option value="STORED_MUSIC">Stored Music</option>
|
||||
<option value="AUX1">AUX1</option>
|
||||
<option value="AUX2">AUX2</option>
|
||||
<option value="AUX3">AUX3</option>
|
||||
<option value="SPOTIFY">Spotify</option>
|
||||
<option value="PANDORA">Pandora</option>
|
||||
<option value="DEEZER">Deezer</option>
|
||||
<option value="SIRIUSXM">SiriusXM</option>
|
||||
<option value="AMAZON">Amazon</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<!-- Notification channels -->
|
||||
<channel-type id="notificationsound" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Notification Sound</label>
|
||||
<description>Play a notification sound by a given URI</description>
|
||||
<config-description-ref uri="channel-type:bosesoundtouch:notificationSound"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user