mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-02-04 19:34:05 +01:00
[bosesoundtouch] Improve SAT errors and remove dependency (#13842)
Signed-off-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
parent
92310fab2f
commit
db86d291da
@ -12,11 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link APIRequest} class handles the API requests
|
* The {@link APIRequest} class handles the API requests
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
public enum APIRequest {
|
public enum APIRequest {
|
||||||
KEY("key"),
|
KEY("key"),
|
||||||
SELECT("select"),
|
SELECT("select"),
|
||||||
|
@ -12,11 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link AvailableSources} is used to find out, which sources and functions are available
|
* The {@link AvailableSources} is used to find out, which sources and functions are available
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
public interface AvailableSources {
|
public interface AvailableSources {
|
||||||
|
|
||||||
public boolean isBluetoothAvailable();
|
public boolean isBluetoothAvailable();
|
||||||
|
@ -19,6 +19,7 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,6 +29,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class BoseSoundTouchBindingConstants {
|
public class BoseSoundTouchBindingConstants {
|
||||||
|
|
||||||
public static final String BINDING_ID = "bosesoundtouch";
|
public static final String BINDING_ID = "bosesoundtouch";
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +21,7 @@ import org.openhab.core.thing.Thing;
|
|||||||
*
|
*
|
||||||
* @author Ivaylo Ivanov - Initial contribution
|
* @author Ivaylo Ivanov - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class BoseSoundTouchConfiguration {
|
public class BoseSoundTouchConfiguration {
|
||||||
|
|
||||||
// Device configuration parameters;
|
// Device configuration parameters;
|
||||||
@ -26,10 +29,10 @@ public class BoseSoundTouchConfiguration {
|
|||||||
public static final String MAC_ADDRESS = Thing.PROPERTY_MAC_ADDRESS;
|
public static final String MAC_ADDRESS = Thing.PROPERTY_MAC_ADDRESS;
|
||||||
public static final String APP_KEY = "appKey";
|
public static final String APP_KEY = "appKey";
|
||||||
|
|
||||||
public String host;
|
public @Nullable String host;
|
||||||
public String macAddress;
|
public @Nullable String macAddress;
|
||||||
public String appKey;
|
public @Nullable String appKey;
|
||||||
|
|
||||||
// Not an actual configuration field, but it will contain the name of the group (in case of Stereo Pair)
|
// Not an actual configuration field, but it will contain the name of the group (in case of Stereo Pair)
|
||||||
public String groupName;
|
public String groupName = "";
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ package org.openhab.binding.bosesoundtouch.internal;
|
|||||||
|
|
||||||
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||||
import org.openhab.core.storage.Storage;
|
import org.openhab.core.storage.Storage;
|
||||||
import org.openhab.core.storage.StorageService;
|
import org.openhab.core.storage.StorageService;
|
||||||
@ -31,11 +33,12 @@ import org.osgi.service.component.annotations.Reference;
|
|||||||
*
|
*
|
||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bosesoundtouch")
|
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.bosesoundtouch")
|
||||||
public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory {
|
public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
private StorageService storageService;
|
private @Nullable StorageService storageService;
|
||||||
private BoseStateDescriptionOptionProvider stateOptionProvider;
|
private @Nullable BoseStateDescriptionOptionProvider stateOptionProvider;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
@ -43,13 +46,20 @@ public class BoseSoundTouchHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
Storage<ContentItem> storage = storageService.getStorage(thing.getUID().toString(),
|
StorageService localStorageService = storageService;
|
||||||
|
if (localStorageService != null) {
|
||||||
|
Storage<ContentItem> storage = localStorageService.getStorage(thing.getUID().toString(),
|
||||||
ContentItem.class.getClassLoader());
|
ContentItem.class.getClassLoader());
|
||||||
|
BoseStateDescriptionOptionProvider localDescriptionOptionProvider = stateOptionProvider;
|
||||||
|
if (localDescriptionOptionProvider != null) {
|
||||||
BoseSoundTouchHandler handler = new BoseSoundTouchHandler(thing, new PresetContainer(storage),
|
BoseSoundTouchHandler handler = new BoseSoundTouchHandler(thing, new PresetContainer(storage),
|
||||||
stateOptionProvider);
|
localDescriptionOptionProvider);
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Reference
|
@Reference
|
||||||
protected void setStorageService(StorageService storageService) {
|
protected void setStorageService(StorageService storageService) {
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link BoseSoundTouchNotFoundException} class is an exception
|
* The {@link BoseSoundTouchNotFoundException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class BoseSoundTouchNotFoundException extends Exception {
|
public class BoseSoundTouchNotFoundException extends Exception {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,11 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration class for soundtouch notification channel
|
* Configuration class for soundtouch notification channel
|
||||||
*
|
*
|
||||||
* @author Ivaylo Ivanov - Initial contribution
|
* @author Ivaylo Ivanov - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class BoseSoundTouchNotificationChannelConfiguration {
|
public class BoseSoundTouchNotificationChannelConfiguration {
|
||||||
|
|
||||||
public static final String MIN_FIRMWARE = "14";
|
public static final String MIN_FIRMWARE = "14";
|
||||||
@ -27,13 +31,13 @@ public class BoseSoundTouchNotificationChannelConfiguration {
|
|||||||
public static final String NOTIFICATION_REASON = "notificationReason";
|
public static final String NOTIFICATION_REASON = "notificationReason";
|
||||||
public static final String NOTIFICATION_MESSAGE = "notificationMessage";
|
public static final String NOTIFICATION_MESSAGE = "notificationMessage";
|
||||||
|
|
||||||
public Integer notificationVolume;
|
public @Nullable Integer notificationVolume;
|
||||||
public String notificationService;
|
public @Nullable String notificationService;
|
||||||
public String notificationReason;
|
public @Nullable String notificationReason;
|
||||||
public String notificationMessage;
|
public @Nullable String notificationMessage;
|
||||||
|
|
||||||
public static boolean isSupportedFirmware(String firmware) {
|
public static boolean isSupportedFirmware(String firmware) {
|
||||||
return firmware != null && firmware.compareTo(MIN_FIRMWARE) > 0;
|
return firmware.compareTo(MIN_FIRMWARE) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupportedHardware(String hardware) {
|
public static boolean isSupportedHardware(String hardware) {
|
||||||
|
@ -18,6 +18,9 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.NextPreviousType;
|
import org.openhab.core.library.types.NextPreviousType;
|
||||||
@ -36,16 +39,17 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
* @author Kai Kreuzer - code clean up
|
* @author Kai Kreuzer - code clean up
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class CommandExecutor implements AvailableSources {
|
public class CommandExecutor implements AvailableSources {
|
||||||
private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
|
private final Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
|
||||||
|
|
||||||
private final BoseSoundTouchHandler handler;
|
private final BoseSoundTouchHandler handler;
|
||||||
|
|
||||||
private boolean currentMuted;
|
private boolean currentMuted;
|
||||||
private ContentItem currentContentItem;
|
private @Nullable ContentItem currentContentItem = null;
|
||||||
private OperationModeType currentOperationMode;
|
private @Nullable OperationModeType currentOperationMode;
|
||||||
|
|
||||||
private Map<String, Boolean> mapOfAvailableFunctions;
|
private final Map<String, Boolean> mapOfAvailableFunctions = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of this class
|
* Creates a new instance of this class
|
||||||
@ -54,7 +58,8 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
*/
|
*/
|
||||||
public CommandExecutor(BoseSoundTouchHandler handler) {
|
public CommandExecutor(BoseSoundTouchHandler handler) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
init();
|
getInformations(APIRequest.INFO);
|
||||||
|
currentOperationMode = OperationModeType.OFFLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,11 +71,7 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
public void updatePresetContainerFromPlayer(Map<Integer, ContentItem> playerPresets) {
|
public void updatePresetContainerFromPlayer(Map<Integer, ContentItem> playerPresets) {
|
||||||
playerPresets.forEach((k, v) -> {
|
playerPresets.forEach((k, v) -> {
|
||||||
try {
|
try {
|
||||||
if (v != null) {
|
|
||||||
handler.getPresetContainer().put(k, v);
|
handler.getPresetContainer().put(k, v);
|
||||||
} else {
|
|
||||||
handler.getPresetContainer().remove(k);
|
|
||||||
}
|
|
||||||
} catch (ContentItemNotPresetableException e) {
|
} catch (ContentItemNotPresetableException e) {
|
||||||
logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
|
logger.debug("{}: ContentItem is not presetable", handler.getDeviceName());
|
||||||
}
|
}
|
||||||
@ -104,7 +105,10 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
*/
|
*/
|
||||||
public void addCurrentContentItemToPresetContainer(DecimalType command) {
|
public void addCurrentContentItemToPresetContainer(DecimalType command) {
|
||||||
if (command.intValue() > 6) {
|
if (command.intValue() > 6) {
|
||||||
addContentItemToPresetContainer(command.intValue(), currentContentItem);
|
ContentItem localContentItem = currentContentItem;
|
||||||
|
if (localContentItem != null) {
|
||||||
|
addContentItemToPresetContainer(command.intValue(), localContentItem);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName());
|
logger.warn("{}: Only PresetID >6 is allowed", handler.getDeviceName());
|
||||||
}
|
}
|
||||||
@ -118,9 +122,12 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
public void getInformations(APIRequest apiRequest) {
|
public void getInformations(APIRequest apiRequest) {
|
||||||
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
|
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + apiRequest
|
||||||
+ "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></msg>";
|
+ "\" method=\"GET\"><request requestID=\"0\"><info type=\"new\"/></request></header></msg>";
|
||||||
handler.getSession().getRemote().sendStringByFuture(msg);
|
Session localSession = handler.getSession();
|
||||||
|
if (localSession != null) {
|
||||||
|
localSession.getRemote().sendStringByFuture(msg);
|
||||||
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current ContentItem if it is valid, and inits an update of the operating values
|
* Sets the current ContentItem if it is valid, and inits an update of the operating values
|
||||||
@ -128,17 +135,19 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
* @param contentItem
|
* @param contentItem
|
||||||
*/
|
*/
|
||||||
public void setCurrentContentItem(ContentItem contentItem) {
|
public void setCurrentContentItem(ContentItem contentItem) {
|
||||||
if ((contentItem != null) && (contentItem.isValid())) {
|
if (contentItem.isValid()) {
|
||||||
ContentItem psFound = null;
|
ContentItem psFound = null;
|
||||||
if (handler.getPresetContainer() != null) {
|
|
||||||
Collection<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
|
Collection<ContentItem> listOfPresets = handler.getPresetContainer().getAllPresets();
|
||||||
for (ContentItem ps : listOfPresets) {
|
for (ContentItem ps : listOfPresets) {
|
||||||
if (ps.isPresetable()) {
|
if (ps.isPresetable()) {
|
||||||
if (ps.getLocation().equals(contentItem.getLocation())) {
|
String localLocation = ps.getLocation();
|
||||||
|
if (localLocation != null) {
|
||||||
|
if (localLocation.equals(contentItem.getLocation())) {
|
||||||
psFound = ps;
|
psFound = ps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int presetID = 0;
|
int presetID = 0;
|
||||||
if (psFound != null) {
|
if (psFound != null) {
|
||||||
presetID = psFound.getPresetID();
|
presetID = psFound.getPresetID();
|
||||||
@ -146,7 +155,7 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
contentItem.setPresetID(presetID);
|
contentItem.setPresetID(presetID);
|
||||||
|
|
||||||
currentContentItem = contentItem;
|
currentContentItem = contentItem;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updateOperatingValues();
|
updateOperatingValues();
|
||||||
}
|
}
|
||||||
@ -350,20 +359,10 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
handler.updateState(CHANNEL_PRESET, 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) {
|
private void postContentItem(ContentItem contentItem) {
|
||||||
if (contentItem != null) {
|
|
||||||
setCurrentContentItem(contentItem);
|
setCurrentContentItem(contentItem);
|
||||||
sendPostRequestInWebSocket("select", "", contentItem.generateXML());
|
sendPostRequestInWebSocket("select", "", contentItem.generateXML());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPostRequestInWebSocket(String url, String postData) {
|
private void sendPostRequestInWebSocket(String url, String postData) {
|
||||||
sendPostRequestInWebSocket(url, "", postData);
|
sendPostRequestInWebSocket(url, "", postData);
|
||||||
@ -374,19 +373,21 @@ public class CommandExecutor implements AvailableSources {
|
|||||||
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
|
String msg = "<msg><header " + "deviceID=\"" + handler.getMacAddress() + "\"" + " url=\"" + url
|
||||||
+ "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
|
+ "\" method=\"POST\"><request requestID=\"" + id + "\"><info " + infoAddon
|
||||||
+ " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
|
+ " type=\"new\"/></request></header><body>" + postData + "</body></msg>";
|
||||||
try {
|
Session localSession = handler.getSession();
|
||||||
handler.getSession().getRemote().sendStringByFuture(msg);
|
if (localSession != null) {
|
||||||
|
localSession.getRemote().sendStringByFuture(msg);
|
||||||
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
logger.debug("{}: sending request: {}", handler.getDeviceName(), msg);
|
||||||
} catch (NullPointerException e) {
|
} else {
|
||||||
handler.onWebSocketError(e);
|
handler.onWebSocketError(new NullPointerException("NPE: Session is unexpected null"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOperatingValues() {
|
private void updateOperatingValues() {
|
||||||
OperationModeType operationMode;
|
OperationModeType operationMode;
|
||||||
if (currentContentItem != null) {
|
ContentItem localContentItem = currentContentItem;
|
||||||
updatePresetGUIState(new DecimalType(currentContentItem.getPresetID()));
|
if (localContentItem != null) {
|
||||||
operationMode = currentContentItem.getOperationMode();
|
updatePresetGUIState(new DecimalType(localContentItem.getPresetID()));
|
||||||
|
operationMode = localContentItem.getOperationMode();
|
||||||
} else {
|
} else {
|
||||||
operationMode = OperationModeType.STANDBY;
|
operationMode = OperationModeType.STANDBY;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringEscapeUtils;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.types.StateOption;
|
import org.openhab.core.types.StateOption;
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
@ -27,31 +28,18 @@ import com.google.gson.annotations.Expose;
|
|||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class ContentItem {
|
public class ContentItem {
|
||||||
|
|
||||||
private String source;
|
private String source = "";
|
||||||
private String sourceAccount;
|
private @Nullable String sourceAccount;
|
||||||
private String location;
|
private @Nullable String location;
|
||||||
private boolean presetable;
|
private boolean presetable = false;
|
||||||
private String itemName;
|
private @Nullable String itemName;
|
||||||
private int presetID;
|
private int presetID = 0;
|
||||||
private String containerArt;
|
private @Nullable String containerArt;
|
||||||
@Expose
|
@Expose
|
||||||
private final Map<String, String> additionalAttributes;
|
private final Map<String, String> additionalAttributes = new HashMap<>();
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Returns true if this ContentItem is defined as Preset
|
||||||
@ -74,11 +62,13 @@ public class ContentItem {
|
|||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
if (getOperationMode() == OperationModeType.STANDBY) {
|
if (getOperationMode() == OperationModeType.STANDBY) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
if (itemName == null || source == null || itemName.isEmpty() || source.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
return true;
|
String localItemName = itemName;
|
||||||
|
if (localItemName != null) {
|
||||||
|
return !(localItemName.isEmpty() || source.isEmpty());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,25 +78,12 @@ public class ContentItem {
|
|||||||
* @return true if source, sourceAccount, location, itemName, and presetable are equal
|
* @return true if source, sourceAccount, location, itemName, and presetable are equal
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (obj instanceof ContentItem) {
|
if (obj instanceof ContentItem) {
|
||||||
ContentItem other = (ContentItem) obj;
|
ContentItem other = (ContentItem) obj;
|
||||||
if (!Objects.equals(other.source, this.source)) {
|
return Objects.equals(other.source, this.source) || Objects.equals(other.sourceAccount, this.sourceAccount)
|
||||||
return false;
|
|| other.presetable == this.presetable || Objects.equals(other.location, this.location)
|
||||||
}
|
|| Objects.equals(other.itemName, this.itemName);
|
||||||
if (!Objects.equals(other.sourceAccount, this.sourceAccount)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (other.presetable != this.presetable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Objects.equals(other.location, this.location)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!Objects.equals(other.itemName, this.itemName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return super.equals(obj);
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
@ -118,16 +95,19 @@ public class ContentItem {
|
|||||||
*/
|
*/
|
||||||
public OperationModeType getOperationMode() {
|
public OperationModeType getOperationMode() {
|
||||||
OperationModeType operationMode = OperationModeType.OTHER;
|
OperationModeType operationMode = OperationModeType.OTHER;
|
||||||
if (source == null || source.equals("")) {
|
if ("".equals(source)) {
|
||||||
return OperationModeType.OTHER;
|
return OperationModeType.OTHER;
|
||||||
}
|
}
|
||||||
if (source.contains("PRODUCT")) {
|
if (source.contains("PRODUCT")) {
|
||||||
if (sourceAccount.contains("TV")) {
|
String localSourceAccount = sourceAccount;
|
||||||
|
if (localSourceAccount != null) {
|
||||||
|
if (localSourceAccount.contains("TV")) {
|
||||||
operationMode = OperationModeType.TV;
|
operationMode = OperationModeType.TV;
|
||||||
}
|
}
|
||||||
if (sourceAccount.contains("HDMI")) {
|
if (localSourceAccount.contains("HDMI")) {
|
||||||
operationMode = OperationModeType.HDMI1;
|
operationMode = OperationModeType.HDMI1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return operationMode;
|
return operationMode;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -174,15 +154,15 @@ public class ContentItem {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceAccount() {
|
public @Nullable String getSourceAccount() {
|
||||||
return sourceAccount;
|
return sourceAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLocation() {
|
public @Nullable String getLocation() {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getItemName() {
|
public @Nullable String getItemName() {
|
||||||
return itemName;
|
return itemName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,10 +174,28 @@ public class ContentItem {
|
|||||||
return presetID;
|
return presetID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getContainerArt() {
|
public @Nullable String getContainerArt() {
|
||||||
return containerArt;
|
return containerArt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple method to escape XML special characters in String.
|
||||||
|
* There are five XML Special characters which needs to be escaped :
|
||||||
|
* & - &
|
||||||
|
* < - <
|
||||||
|
* > - >
|
||||||
|
* " - "
|
||||||
|
* ' - '
|
||||||
|
*/
|
||||||
|
private String escapeXml(String xml) {
|
||||||
|
xml = xml.replaceAll("&", "&");
|
||||||
|
xml = xml.replaceAll("<", "<");
|
||||||
|
xml = xml.replaceAll(">", ">");
|
||||||
|
xml = xml.replaceAll("\"", """);
|
||||||
|
xml = xml.replaceAll("'", "'");
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the XML Code that is needed to switch to this ContentItem
|
* Returns the XML Code that is needed to switch to this ContentItem
|
||||||
*
|
*
|
||||||
@ -223,19 +221,20 @@ public class ContentItem {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
StringBuilder sbXml = new StringBuilder("<ContentItem");
|
StringBuilder sbXml = new StringBuilder("<ContentItem");
|
||||||
if (source != null) {
|
|
||||||
sbXml.append(" source=\"").append(StringEscapeUtils.escapeXml(source)).append("\"");
|
sbXml.append(" source=\"").append(escapeXml(source)).append("\"");
|
||||||
|
|
||||||
|
String localLocation = location;
|
||||||
|
if (localLocation != null) {
|
||||||
|
sbXml.append(" location=\"").append(escapeXml(localLocation)).append("\"");
|
||||||
}
|
}
|
||||||
if (location != null) {
|
String localSourceAccount = sourceAccount;
|
||||||
sbXml.append(" location=\"").append(StringEscapeUtils.escapeXml(location)).append("\"");
|
if (localSourceAccount != null) {
|
||||||
}
|
sbXml.append(" sourceAccount=\"").append(escapeXml(localSourceAccount)).append("\"");
|
||||||
if (sourceAccount != null) {
|
|
||||||
sbXml.append(" sourceAccount=\"").append(StringEscapeUtils.escapeXml(sourceAccount)).append("\"");
|
|
||||||
}
|
}
|
||||||
sbXml.append(" isPresetable=\"").append(presetable).append("\"");
|
sbXml.append(" isPresetable=\"").append(presetable).append("\"");
|
||||||
for (Map.Entry<String, String> aae : additionalAttributes.entrySet()) {
|
for (Map.Entry<String, String> aae : additionalAttributes.entrySet()) {
|
||||||
sbXml.append(" ").append(aae.getKey()).append("=\"")
|
sbXml.append(" ").append(aae.getKey()).append("=\"").append(escapeXml(aae.getValue())).append("\"");
|
||||||
.append(StringEscapeUtils.escapeXml(aae.getValue())).append("\"");
|
|
||||||
}
|
}
|
||||||
sbXml.append(">");
|
sbXml.append(">");
|
||||||
if (itemName != null) {
|
if (itemName != null) {
|
||||||
@ -264,6 +263,7 @@ public class ContentItem {
|
|||||||
// buffer.append(presetID);
|
// buffer.append(presetID);
|
||||||
// return buffer.toString();
|
// return buffer.toString();
|
||||||
// }
|
// }
|
||||||
return itemName;
|
String localString = itemName;
|
||||||
|
return (localString != null) ? localString : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,14 @@ package org.openhab.binding.bosesoundtouch.internal;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ContentItemMaker} class makes ContentItems for sources
|
* The {@link ContentItemMaker} class makes ContentItems for sources
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class ContentItemMaker {
|
public class ContentItemMaker {
|
||||||
|
|
||||||
private final PresetContainer presetContainer;
|
private final PresetContainer presetContainer;
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ContentItemNotPresetableException} class is an exception
|
* The {@link ContentItemNotPresetableException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class ContentItemNotPresetableException extends NoPresetFoundException {
|
public class ContentItemNotPresetableException extends NoPresetFoundException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NoInternetRadioPresetFoundException} class is an exception
|
* The {@link NoInternetRadioPresetFoundException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class NoInternetRadioPresetFoundException extends NoPresetFoundException {
|
public class NoInternetRadioPresetFoundException extends NoPresetFoundException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NoPresetFoundException} class is an exception
|
* The {@link NoPresetFoundException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class NoPresetFoundException extends Exception {
|
public class NoPresetFoundException extends Exception {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NoStoredMusicPresetFoundException} class is an exception
|
* The {@link NoStoredMusicPresetFoundException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class NoStoredMusicPresetFoundException extends NoPresetFoundException {
|
public class NoStoredMusicPresetFoundException extends NoPresetFoundException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OperationModeNotAvailableException} class is an exception
|
* The {@link OperationModeNotAvailableException} class is an exception
|
||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class OperationModeNotAvailableException extends Exception {
|
public class OperationModeNotAvailableException extends Exception {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -12,12 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OperationModeType} class is holding all OperationModes
|
* The {@link OperationModeType} class is holding all OperationModes
|
||||||
*
|
*
|
||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public enum OperationModeType {
|
public enum OperationModeType {
|
||||||
OFFLINE,
|
OFFLINE,
|
||||||
STANDBY,
|
STANDBY,
|
||||||
|
@ -17,8 +17,11 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.core.storage.DeletableStorage;
|
import org.openhab.core.storage.DeletableStorage;
|
||||||
import org.openhab.core.storage.Storage;
|
import org.openhab.core.storage.Storage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -30,11 +33,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
* @author Kai Kreuzer - Refactored it to use storage instead of file
|
* @author Kai Kreuzer - Refactored it to use storage instead of file
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class PresetContainer {
|
public class PresetContainer {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(PresetContainer.class);
|
private final Logger logger = LoggerFactory.getLogger(PresetContainer.class);
|
||||||
|
|
||||||
private HashMap<Integer, ContentItem> mapOfPresets;
|
private final Map<Integer, ContentItem> mapOfPresets = new HashMap<>();
|
||||||
private Storage<ContentItem> storage;
|
private Storage<ContentItem> storage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,11 +46,6 @@ public class PresetContainer {
|
|||||||
*/
|
*/
|
||||||
public PresetContainer(Storage<ContentItem> storage) {
|
public PresetContainer(Storage<ContentItem> storage) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
this.mapOfPresets = new HashMap<>();
|
|
||||||
readFromStorage();
|
readFromStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,10 +132,12 @@ public class PresetContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readFromStorage() {
|
private void readFromStorage() {
|
||||||
Collection<ContentItem> items = storage.getValues();
|
Collection<@Nullable ContentItem> items = storage.getValues();
|
||||||
for (ContentItem item : items) {
|
for (ContentItem item : items) {
|
||||||
try {
|
try {
|
||||||
|
if (item != null) {
|
||||||
put(item.getPresetID(), item);
|
put(item.getPresetID(), item);
|
||||||
|
}
|
||||||
} catch (ContentItemNotPresetableException e) {
|
} catch (ContentItemNotPresetableException e) {
|
||||||
logger.debug("Item '{}' is not presetable - ignoring it.", item.getItemName());
|
logger.debug("Item '{}' is not presetable - ignoring it.", item.getItemName());
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link RemoteKeyType} class is holding the Keys on a remote. For simulating key presses
|
* The {@link RemoteKeyType} class is holding the Keys on a remote. For simulating key presses
|
||||||
*
|
*
|
||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public enum RemoteKeyType {
|
public enum RemoteKeyType {
|
||||||
PLAY,
|
PLAY,
|
||||||
PAUSE,
|
PAUSE,
|
||||||
|
@ -12,12 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.bosesoundtouch.internal;
|
package org.openhab.binding.bosesoundtouch.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link XMLHandlerState} class defines the XML States provided from Bose Soundtouch
|
* The {@link XMLHandlerState} class defines the XML States provided from Bose Soundtouch
|
||||||
*
|
*
|
||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public enum XMLHandlerState {
|
public enum XMLHandlerState {
|
||||||
INIT,
|
INIT,
|
||||||
Msg,
|
Msg,
|
||||||
|
@ -22,6 +22,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
@ -54,8 +55,8 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
|
|
||||||
private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
|
private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
|
||||||
|
|
||||||
private Stack<XMLHandlerState> states;
|
private final Stack<XMLHandlerState> states = new Stack<>();
|
||||||
private XMLHandlerState state;
|
private XMLHandlerState state = XMLHandlerState.INIT;
|
||||||
private boolean msgHeaderWasValid;
|
private boolean msgHeaderWasValid;
|
||||||
|
|
||||||
private ContentItem contentItem;
|
private ContentItem contentItem;
|
||||||
@ -63,10 +64,10 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
private OnOffType rateEnabled;
|
private OnOffType rateEnabled;
|
||||||
private OnOffType skipEnabled;
|
private OnOffType skipEnabled;
|
||||||
private OnOffType skipPreviousEnabled;
|
private OnOffType skipPreviousEnabled;
|
||||||
|
|
||||||
private State nowPlayingSource;
|
private State nowPlayingSource;
|
||||||
|
|
||||||
private BoseSoundTouchConfiguration masterDeviceId;
|
private BoseSoundTouchConfiguration masterDeviceId;
|
||||||
|
|
||||||
String deviceId;
|
String deviceId;
|
||||||
|
|
||||||
private Map<Integer, ContentItem> playerPresets;
|
private Map<Integer, ContentItem> playerPresets;
|
||||||
@ -82,11 +83,11 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.commandExecutor = handler.getCommandExecutor();
|
this.commandExecutor = handler.getCommandExecutor();
|
||||||
this.stateSwitchingMap = stateSwitchingMap;
|
this.stateSwitchingMap = stateSwitchingMap;
|
||||||
init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
|
public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName,
|
||||||
|
@Nullable Attributes attributes) throws SAXException {
|
||||||
super.startElement(uri, localName, qName, attributes);
|
super.startElement(uri, localName, qName, attributes);
|
||||||
logger.trace("{}: startElement('{}'; state: {})", handler.getDeviceName(), localName, state);
|
logger.trace("{}: startElement('{}'; state: {})", handler.getDeviceName(), localName, state);
|
||||||
states.push(state);
|
states.push(state);
|
||||||
@ -95,6 +96,12 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
state = XMLHandlerState.Unprocessed; // set default value; we avoid default in select to have the compiler
|
state = XMLHandlerState.Unprocessed; // set default value; we avoid default in select to have the compiler
|
||||||
// showing a
|
// showing a
|
||||||
// warning for unhandled states
|
// warning for unhandled states
|
||||||
|
|
||||||
|
XMLHandlerState localState = null;
|
||||||
|
if (stateMap != null) {
|
||||||
|
localState = stateMap.get(localName);
|
||||||
|
}
|
||||||
|
|
||||||
switch (curState) {
|
switch (curState) {
|
||||||
case INIT:
|
case INIT:
|
||||||
if ("updates".equals(localName)) {
|
if ("updates".equals(localName)) {
|
||||||
@ -105,12 +112,9 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state = stateMap.get(localName);
|
if (localState == null) {
|
||||||
if (state == null) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,10 +135,9 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -142,10 +145,8 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
if ("request".equals(localName)) {
|
if ("request".equals(localName)) {
|
||||||
state = XMLHandlerState.Unprocessed; // TODO implement request id / response tracking...
|
state = XMLHandlerState.Unprocessed; // TODO implement request id / response tracking...
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -161,7 +162,10 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
skipEnabled = OnOffType.OFF;
|
skipEnabled = OnOffType.OFF;
|
||||||
skipPreviousEnabled = OnOffType.OFF;
|
skipPreviousEnabled = OnOffType.OFF;
|
||||||
state = XMLHandlerState.NowPlaying;
|
state = XMLHandlerState.NowPlaying;
|
||||||
String source = attributes.getValue("source");
|
String source = "";
|
||||||
|
if (attributes != null) {
|
||||||
|
source = attributes.getValue("source");
|
||||||
|
}
|
||||||
if (nowPlayingSource == null || !nowPlayingSource.toString().equals(source)) {
|
if (nowPlayingSource == null || !nowPlayingSource.toString().equals(source)) {
|
||||||
// source changed
|
// source changed
|
||||||
nowPlayingSource = new StringType(source);
|
nowPlayingSource = new StringType(source);
|
||||||
@ -192,14 +196,11 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
state = XMLHandlerState.Presets;
|
state = XMLHandlerState.Presets;
|
||||||
} else if ("group".equals(localName)) {
|
} else if ("group".equals(localName)) {
|
||||||
this.masterDeviceId = new BoseSoundTouchConfiguration();
|
this.masterDeviceId = new BoseSoundTouchConfiguration();
|
||||||
state = stateMap.get(localName);
|
|
||||||
} else {
|
} else {
|
||||||
state = stateMap.get(localName);
|
if (localState == null) {
|
||||||
if (state == null) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
} else if (state != XMLHandlerState.Volume && state != XMLHandlerState.Presets
|
} else if (state != XMLHandlerState.Volume && state != XMLHandlerState.Presets
|
||||||
&& state != XMLHandlerState.Group && state != XMLHandlerState.Unprocessed) {
|
&& state != XMLHandlerState.Group && state != XMLHandlerState.Unprocessed) {
|
||||||
@ -213,63 +214,78 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
case Presets:
|
case Presets:
|
||||||
if ("preset".equals(localName)) {
|
if ("preset".equals(localName)) {
|
||||||
state = XMLHandlerState.Preset;
|
state = XMLHandlerState.Preset;
|
||||||
String id = attributes.getValue("id");
|
String id = "0";
|
||||||
|
if (attributes != null) {
|
||||||
|
id = attributes.getValue("id");
|
||||||
|
}
|
||||||
if (contentItem == null) {
|
if (contentItem == null) {
|
||||||
contentItem = new ContentItem();
|
contentItem = new ContentItem();
|
||||||
}
|
}
|
||||||
contentItem.setPresetID(Integer.parseInt(id));
|
contentItem.setPresetID(Integer.parseInt(id));
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Sources:
|
case Sources:
|
||||||
if ("sourceItem".equals(localName)) {
|
if ("sourceItem".equals(localName)) {
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
String source = attributes.getValue("source");
|
String source = "";
|
||||||
String sourceAccount = attributes.getValue("sourceAccount");
|
String status = "";
|
||||||
String status = attributes.getValue("status");
|
String sourceAccount = "";
|
||||||
if (status.equals("READY")) {
|
if (attributes != null) {
|
||||||
if (source.equals("AUX")) {
|
source = attributes.getValue("source");
|
||||||
if (sourceAccount.equals("AUX")) {
|
sourceAccount = attributes.getValue("sourceAccount");
|
||||||
|
status = attributes.getValue("status");
|
||||||
|
}
|
||||||
|
if ("READY".equals(status)) {
|
||||||
|
switch (source) {
|
||||||
|
case "AUX":
|
||||||
|
if ("AUX".equals(sourceAccount)) {
|
||||||
commandExecutor.setAUXAvailable(true);
|
commandExecutor.setAUXAvailable(true);
|
||||||
}
|
}
|
||||||
if (sourceAccount.equals("AUX1")) {
|
if ("AUX1".equals(sourceAccount)) {
|
||||||
commandExecutor.setAUX1Available(true);
|
commandExecutor.setAUX1Available(true);
|
||||||
}
|
}
|
||||||
if (sourceAccount.equals("AUX2")) {
|
if ("AUX2".equals(sourceAccount)) {
|
||||||
commandExecutor.setAUX2Available(true);
|
commandExecutor.setAUX2Available(true);
|
||||||
}
|
}
|
||||||
if (sourceAccount.equals("AUX3")) {
|
if ("AUX3".equals(sourceAccount)) {
|
||||||
commandExecutor.setAUX3Available(true);
|
commandExecutor.setAUX3Available(true);
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
if (source.equals("STORED_MUSIC")) {
|
case "STORED_MUSIC":
|
||||||
commandExecutor.setStoredMusicAvailable(true);
|
commandExecutor.setStoredMusicAvailable(true);
|
||||||
}
|
break;
|
||||||
if (source.equals("INTERNET_RADIO")) {
|
case "INTERNET_RADIO":
|
||||||
commandExecutor.setInternetRadioAvailable(true);
|
commandExecutor.setInternetRadioAvailable(true);
|
||||||
}
|
break;
|
||||||
if (source.equals("BLUETOOTH")) {
|
case "BLUETOOTH":
|
||||||
commandExecutor.setBluetoothAvailable(true);
|
commandExecutor.setBluetoothAvailable(true);
|
||||||
}
|
break;
|
||||||
if (source.equals("PRODUCT")) {
|
case "PRODUCT":
|
||||||
if (sourceAccount.equals("TV")) {
|
switch (sourceAccount) {
|
||||||
|
case "TV":
|
||||||
commandExecutor.setTVAvailable(true);
|
commandExecutor.setTVAvailable(true);
|
||||||
}
|
break;
|
||||||
if (sourceAccount.equals("HDMI_1")) {
|
case "HDMI_1":
|
||||||
commandExecutor.setHDMI1Available(true);
|
commandExecutor.setHDMI1Available(true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.debug("{}: has an unknown source account: '{}'", handler.getDeviceName(),
|
||||||
|
sourceAccount);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
logger.debug("{}: has an unknown source: '{}'", handler.getDeviceName(), source);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
||||||
logger.warn("{}: Unhandled XML entity during {}: '{}'", handler.getDeviceName(), curState,
|
|
||||||
localName);
|
localName);
|
||||||
}
|
|
||||||
state = XMLHandlerState.Unprocessed;
|
state = XMLHandlerState.Unprocessed;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -350,10 +366,28 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
if (contentItem == null) {
|
if (contentItem == null) {
|
||||||
contentItem = new ContentItem();
|
contentItem = new ContentItem();
|
||||||
}
|
}
|
||||||
contentItem.setSource(attributes.getValue("source"));
|
String source = "";
|
||||||
contentItem.setSourceAccount(attributes.getValue("sourceAccount"));
|
String location = "";
|
||||||
contentItem.setLocation(attributes.getValue("location"));
|
String sourceAccount = "";
|
||||||
contentItem.setPresetable(Boolean.parseBoolean(attributes.getValue("isPresetable")));
|
Boolean isPresetable = false;
|
||||||
|
|
||||||
|
if (attributes != null) {
|
||||||
|
source = attributes.getValue("source");
|
||||||
|
sourceAccount = attributes.getValue("sourceAccount");
|
||||||
|
location = attributes.getValue("location");
|
||||||
|
isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable"));
|
||||||
|
|
||||||
|
if (source != null) {
|
||||||
|
contentItem.setSource(source);
|
||||||
|
}
|
||||||
|
if (sourceAccount != null) {
|
||||||
|
contentItem.setSourceAccount(sourceAccount);
|
||||||
|
}
|
||||||
|
if (location != null) {
|
||||||
|
contentItem.setLocation(location);
|
||||||
|
}
|
||||||
|
contentItem.setPresetable(isPresetable);
|
||||||
|
|
||||||
for (int attrId = 0; attrId < attributes.getLength(); attrId++) {
|
for (int attrId = 0; attrId < attributes.getLength(); attrId++) {
|
||||||
String attrName = attributes.getLocalName(attrId);
|
String attrName = attributes.getLocalName(attrId);
|
||||||
if ("source".equalsIgnoreCase(attrName)) {
|
if ("source".equalsIgnoreCase(attrName)) {
|
||||||
@ -368,10 +402,13 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
if ("isPresetable".equalsIgnoreCase(attrName)) {
|
if ("isPresetable".equalsIgnoreCase(attrName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (attrName != null) {
|
||||||
contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId));
|
contentItem.setAdditionalAttribute(attrName, attributes.getValue(attrId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String qName) throws SAXException {
|
public void endElement(String uri, String localName, String qName) throws SAXException {
|
||||||
@ -599,8 +636,9 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
super.skippedEntity(name);
|
super.skippedEntity(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkDeviceId(String localName, Attributes attributes, boolean allowFromMaster) {
|
private boolean checkDeviceId(@Nullable String localName, @Nullable Attributes attributes,
|
||||||
String deviceID = attributes.getValue("deviceID");
|
boolean allowFromMaster) {
|
||||||
|
String deviceID = (attributes != null) ? attributes.getValue("deviceID") : null;
|
||||||
if (deviceID == null) {
|
if (deviceID == null) {
|
||||||
logger.warn("{}: No device-ID in entity {}", handler.getDeviceName(), localName);
|
logger.warn("{}: No device-ID in entity {}", handler.getDeviceName(), localName);
|
||||||
return false;
|
return false;
|
||||||
@ -613,12 +651,6 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
|
||||||
states = new Stack<>();
|
|
||||||
state = XMLHandlerState.INIT;
|
|
||||||
nowPlayingSource = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private XMLHandlerState nextState(Map<String, XMLHandlerState> stateMap, XMLHandlerState curState,
|
private XMLHandlerState nextState(Map<String, XMLHandlerState> stateMap, XMLHandlerState curState,
|
||||||
String localName) {
|
String localName) {
|
||||||
XMLHandlerState state = stateMap.get(localName);
|
XMLHandlerState state = stateMap.get(localName);
|
||||||
@ -632,6 +664,7 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setConfigOption(String option, String value) {
|
private void setConfigOption(String option, String value) {
|
||||||
|
if (option != null) {
|
||||||
Map<String, String> prop = handler.getThing().getProperties();
|
Map<String, String> prop = handler.getThing().getProperties();
|
||||||
String cur = prop.get(option);
|
String cur = prop.get(option);
|
||||||
if (cur == null || !cur.equals(value)) {
|
if (cur == null || !cur.equals(value)) {
|
||||||
@ -639,6 +672,7 @@ public class XMLResponseHandler extends DefaultHandler {
|
|||||||
handler.getThing().setProperty(option, value);
|
handler.getThing().setProperty(option, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateNowPlayingAlbum(State state) {
|
private void updateNowPlayingAlbum(State state) {
|
||||||
handler.updateState(CHANNEL_NOWPLAYING_ALBUM, state);
|
handler.updateState(CHANNEL_NOWPLAYING_ALBUM, state);
|
||||||
|
@ -17,11 +17,15 @@ import java.io.StringReader;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.parsers.SAXParser;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.XMLReader;
|
import org.xml.sax.XMLReader;
|
||||||
import org.xml.sax.helpers.XMLReaderFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link XMLResponseProcessor} class handles the XML mapping
|
* The {@link XMLResponseProcessor} class handles the XML mapping
|
||||||
@ -29,18 +33,22 @@ import org.xml.sax.helpers.XMLReaderFactory;
|
|||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
public class XMLResponseProcessor {
|
public class XMLResponseProcessor {
|
||||||
private BoseSoundTouchHandler handler;
|
private BoseSoundTouchHandler handler;
|
||||||
|
|
||||||
private Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap;
|
private final Map<XMLHandlerState, Map<String, XMLHandlerState>> stateSwitchingMap = new HashMap<>();
|
||||||
|
|
||||||
public XMLResponseProcessor(BoseSoundTouchHandler handler) {
|
public XMLResponseProcessor(BoseSoundTouchHandler handler) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleMessage(String msg) throws SAXException, IOException {
|
public void handleMessage(String msg) throws SAXException, IOException, ParserConfigurationException {
|
||||||
XMLReader reader = XMLReaderFactory.createXMLReader();
|
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
|
||||||
|
SAXParser parser = parserFactory.newSAXParser();
|
||||||
|
XMLReader reader = parser.getXMLReader();
|
||||||
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
reader.setContentHandler(new XMLResponseHandler(handler, stateSwitchingMap));
|
reader.setContentHandler(new XMLResponseHandler(handler, stateSwitchingMap));
|
||||||
reader.parse(new InputSource(new StringReader(msg)));
|
reader.parse(new InputSource(new StringReader(msg)));
|
||||||
@ -48,8 +56,6 @@ public class XMLResponseProcessor {
|
|||||||
|
|
||||||
// initializes our XML parsing state machine
|
// initializes our XML parsing state machine
|
||||||
private void init() {
|
private void init() {
|
||||||
stateSwitchingMap = new HashMap<>();
|
|
||||||
|
|
||||||
Map<String, XMLHandlerState> msgInitMap = new HashMap<>();
|
Map<String, XMLHandlerState> msgInitMap = new HashMap<>();
|
||||||
stateSwitchingMap.put(XMLHandlerState.INIT, msgInitMap);
|
stateSwitchingMap.put(XMLHandlerState.INIT, msgInitMap);
|
||||||
msgInitMap.put("msg", XMLHandlerState.Msg);
|
msgInitMap.put("msg", XMLHandlerState.Msg);
|
||||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.bosesoundtouch.internal.discovery;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,6 +22,7 @@ import org.openhab.core.io.net.http.HttpUtil;
|
|||||||
*
|
*
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class DiscoveryUtil {
|
public class DiscoveryUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,9 +31,6 @@ public class DiscoveryUtil {
|
|||||||
* This is a quick and dirty method, it always delivers the first appearance of 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) {
|
public static String getContentOfFirstElement(String content, String element) {
|
||||||
if (content == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
String beginTag = "<" + element + ">";
|
String beginTag = "<" + element + ">";
|
||||||
String endTag = "</" + element + ">";
|
String endTag = "</" + element + ">";
|
||||||
|
|
||||||
@ -39,7 +38,8 @@ public class DiscoveryUtil {
|
|||||||
int endIndex = content.indexOf(endTag);
|
int endIndex = content.indexOf(endTag);
|
||||||
|
|
||||||
if (startIndex != -1 && endIndex != -1) {
|
if (startIndex != -1 && endIndex != -1) {
|
||||||
return content.substring(startIndex, endIndex);
|
String result = content.substring(startIndex, endIndex);
|
||||||
|
return result != null ? result : "";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ import java.util.Set;
|
|||||||
|
|
||||||
import javax.jmdns.ServiceInfo;
|
import javax.jmdns.ServiceInfo;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
|
import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Christian Niessner - Initial contribution
|
* @author Christian Niessner - Initial contribution
|
||||||
* @author Thomas Traunbauer - Initial contribution
|
* @author Thomas Traunbauer - Initial contribution
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
@Component(configurationPid = "discovery.bosesoundtouch")
|
@Component(configurationPid = "discovery.bosesoundtouch")
|
||||||
public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DiscoveryResult createResult(ServiceInfo info) {
|
public @Nullable DiscoveryResult createResult(ServiceInfo info) {
|
||||||
DiscoveryResult result = null;
|
DiscoveryResult result = null;
|
||||||
ThingUID uid = getThingUID(info);
|
ThingUID uid = getThingUID(info);
|
||||||
if (uid != null) {
|
if (uid != null) {
|
||||||
@ -89,9 +92,10 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
}
|
}
|
||||||
|
|
||||||
properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress());
|
properties.put(BoseSoundTouchConfiguration.HOST, addrs[0].getHostAddress());
|
||||||
if (getMacAddress(info) != null) {
|
byte[] localMacAddress = getMacAddress(info);
|
||||||
|
if (localMacAddress.length > 0) {
|
||||||
properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS,
|
properties.put(BoseSoundTouchConfiguration.MAC_ADDRESS,
|
||||||
new String(getMacAddress(info), StandardCharsets.UTF_8));
|
new String(localMacAddress, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set manufacturer as thing property (if available)
|
// Set manufacturer as thing property (if available)
|
||||||
@ -105,7 +109,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ThingUID getThingUID(ServiceInfo info) {
|
public @Nullable ThingUID getThingUID(ServiceInfo info) {
|
||||||
logger.trace("ServiceInfo: {}", info);
|
logger.trace("ServiceInfo: {}", info);
|
||||||
ThingTypeUID typeUID = getThingTypeUID(info);
|
ThingTypeUID typeUID = getThingTypeUID(info);
|
||||||
if (typeUID != null) {
|
if (typeUID != null) {
|
||||||
@ -113,10 +117,8 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
if (info.getType().equals(getServiceType())) {
|
if (info.getType().equals(getServiceType())) {
|
||||||
logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName());
|
logger.trace("Discovered a Bose SoundTouch thing with name '{}'", info.getName());
|
||||||
byte[] mac = getMacAddress(info);
|
byte[] mac = getMacAddress(info);
|
||||||
if (mac != null) {
|
if (mac.length > 0) {
|
||||||
return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
|
return new ThingUID(typeUID, new String(mac, StandardCharsets.UTF_8));
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,13 +131,13 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
return "_soundtouch._tcp.local.";
|
return "_soundtouch._tcp.local.";
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThingTypeUID getThingTypeUID(ServiceInfo info) {
|
private @Nullable ThingTypeUID getThingTypeUID(ServiceInfo info) {
|
||||||
InetAddress[] addrs = info.getInetAddresses();
|
InetAddress[] addrs = info.getInetAddresses();
|
||||||
if (addrs.length > 0) {
|
if (addrs.length > 0) {
|
||||||
String ip = addrs[0].getHostAddress();
|
String ip = addrs[0].getHostAddress();
|
||||||
String deviceId = null;
|
String deviceId = null;
|
||||||
byte[] mac = getMacAddress(info);
|
byte[] mac = getMacAddress(info);
|
||||||
if (mac != null) {
|
if (mac.length > 0) {
|
||||||
deviceId = new String(mac, StandardCharsets.UTF_8);
|
deviceId = new String(mac, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
String deviceType;
|
String deviceType;
|
||||||
@ -143,6 +145,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
|
String content = DiscoveryUtil.executeUrl("http://" + ip + ":8090/info");
|
||||||
deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
|
deviceType = DiscoveryUtil.getContentOfFirstElement(content, "type");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +166,7 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
return BST_10_THING_TYPE_UID;
|
return BST_10_THING_TYPE_UID;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
logger.debug("Ignoring IOException during Discovery: {}", e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,24 +194,21 @@ public class SoundTouchDiscoveryParticipant implements MDNSDiscoveryParticipant
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getMacAddress(ServiceInfo info) {
|
private byte[] getMacAddress(ServiceInfo info) {
|
||||||
if (info != null) {
|
|
||||||
// sometimes we see empty messages - ignore them
|
// sometimes we see empty messages - ignore them
|
||||||
if (!info.hasData()) {
|
if (!info.hasData()) {
|
||||||
return null;
|
return new byte[0];
|
||||||
}
|
}
|
||||||
byte[] mac = info.getPropertyBytes("MAC");
|
byte[] mac = info.getPropertyBytes("MAC");
|
||||||
if (mac == null) {
|
if (mac == null) {
|
||||||
logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
|
logger.warn("SoundTouch Device {} delivered no MAC address!", info.getName());
|
||||||
return null;
|
return new byte[0];
|
||||||
}
|
}
|
||||||
if (mac.length != 12) {
|
if (mac.length != 12) {
|
||||||
BigInteger bi = new BigInteger(1, mac);
|
BigInteger bi = new BigInteger(1, mac);
|
||||||
logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(),
|
logger.warn("SoundTouch Device {} delivered an invalid MAC address: 0x{}", info.getName(),
|
||||||
String.format("%0" + (mac.length << 1) + "X", bi));
|
String.format("%0" + (mac.length << 1) + "X", bi));
|
||||||
return null;
|
return new byte[0];
|
||||||
}
|
}
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ import java.util.concurrent.ScheduledFuture;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
|
import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
|
||||||
@ -75,6 +77,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Kai Kreuzer - code clean up
|
* @author Kai Kreuzer - code clean up
|
||||||
* @author Alexander Kostadinov - Handling of websocket ping-pong mechanism for thing status check
|
* @author Alexander Kostadinov - Handling of websocket ping-pong mechanism for thing status check
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocketListener, WebSocketFrameListener {
|
public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocketListener, WebSocketFrameListener {
|
||||||
|
|
||||||
private static final int MAX_MISSED_PONGS_COUNT = 2;
|
private static final int MAX_MISSED_PONGS_COUNT = 2;
|
||||||
@ -83,10 +86,10 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(BoseSoundTouchHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(BoseSoundTouchHandler.class);
|
||||||
|
|
||||||
private ScheduledFuture<?> connectionChecker;
|
private @Nullable ScheduledFuture<?> connectionChecker;
|
||||||
private WebSocketClient client;
|
private @Nullable WebSocketClient client;
|
||||||
private volatile Session session;
|
private @Nullable volatile Session session;
|
||||||
private volatile CommandExecutor commandExecutor;
|
private @Nullable volatile CommandExecutor commandExecutor;
|
||||||
private volatile int missedPongsCount = 0;
|
private volatile int missedPongsCount = 0;
|
||||||
|
|
||||||
private XMLResponseProcessor xmlResponseProcessor;
|
private XMLResponseProcessor xmlResponseProcessor;
|
||||||
@ -94,7 +97,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
private PresetContainer presetContainer;
|
private PresetContainer presetContainer;
|
||||||
private BoseStateDescriptionOptionProvider stateOptionProvider;
|
private BoseStateDescriptionOptionProvider stateOptionProvider;
|
||||||
|
|
||||||
private Future<?> sessionFuture;
|
private @Nullable Future<?> sessionFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of this class for the {@link Thing}.
|
* Creates a new instance of this class for the {@link Thing}.
|
||||||
@ -120,10 +123,13 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (connectionChecker != null && !connectionChecker.isCancelled()) {
|
ScheduledFuture<?> localConnectionChecker = connectionChecker;
|
||||||
connectionChecker.cancel(true);
|
if (localConnectionChecker != null) {
|
||||||
|
if (!localConnectionChecker.isCancelled()) {
|
||||||
|
localConnectionChecker.cancel(true);
|
||||||
connectionChecker = null;
|
connectionChecker = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
closeConnection();
|
closeConnection();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -146,7 +152,8 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (commandExecutor == null) {
|
CommandExecutor localCommandExecutor = commandExecutor;
|
||||||
|
if (localCommandExecutor == null) {
|
||||||
logger.debug("{}: Can't handle command '{}' for channel '{}' because of not initialized connection.",
|
logger.debug("{}: Can't handle command '{}' for channel '{}' because of not initialized connection.",
|
||||||
getDeviceName(), command, channelUID);
|
getDeviceName(), command, channelUID);
|
||||||
return;
|
return;
|
||||||
@ -157,7 +164,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
if (command.equals(RefreshType.REFRESH)) {
|
if (command.equals(RefreshType.REFRESH)) {
|
||||||
switch (channelUID.getIdWithoutGroup()) {
|
switch (channelUID.getIdWithoutGroup()) {
|
||||||
case CHANNEL_BASS:
|
case CHANNEL_BASS:
|
||||||
commandExecutor.getInformations(APIRequest.BASS);
|
localCommandExecutor.getInformations(APIRequest.BASS);
|
||||||
break;
|
break;
|
||||||
case CHANNEL_KEY_CODE:
|
case CHANNEL_KEY_CODE:
|
||||||
// refresh makes no sense... ?
|
// refresh makes no sense... ?
|
||||||
@ -174,10 +181,10 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
case CHANNEL_RATEENABLED:
|
case CHANNEL_RATEENABLED:
|
||||||
case CHANNEL_SKIPENABLED:
|
case CHANNEL_SKIPENABLED:
|
||||||
case CHANNEL_SKIPPREVIOUSENABLED:
|
case CHANNEL_SKIPPREVIOUSENABLED:
|
||||||
commandExecutor.getInformations(APIRequest.NOW_PLAYING);
|
localCommandExecutor.getInformations(APIRequest.NOW_PLAYING);
|
||||||
break;
|
break;
|
||||||
case CHANNEL_VOLUME:
|
case CHANNEL_VOLUME:
|
||||||
commandExecutor.getInformations(APIRequest.VOLUME);
|
localCommandExecutor.getInformations(APIRequest.VOLUME);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.debug("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
|
logger.debug("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
|
||||||
@ -188,21 +195,21 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
switch (channelUID.getIdWithoutGroup()) {
|
switch (channelUID.getIdWithoutGroup()) {
|
||||||
case CHANNEL_POWER:
|
case CHANNEL_POWER:
|
||||||
if (command instanceof OnOffType) {
|
if (command instanceof OnOffType) {
|
||||||
commandExecutor.postPower((OnOffType) command);
|
localCommandExecutor.postPower((OnOffType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_VOLUME:
|
case CHANNEL_VOLUME:
|
||||||
if (command instanceof PercentType) {
|
if (command instanceof PercentType) {
|
||||||
commandExecutor.postVolume((PercentType) command);
|
localCommandExecutor.postVolume((PercentType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_MUTE:
|
case CHANNEL_MUTE:
|
||||||
if (command instanceof OnOffType) {
|
if (command instanceof OnOffType) {
|
||||||
commandExecutor.postVolumeMuted((OnOffType) command);
|
localCommandExecutor.postVolumeMuted((OnOffType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
@ -212,7 +219,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
String cmd = command.toString().toUpperCase().trim();
|
String cmd = command.toString().toUpperCase().trim();
|
||||||
try {
|
try {
|
||||||
OperationModeType mode = OperationModeType.valueOf(cmd);
|
OperationModeType mode = OperationModeType.valueOf(cmd);
|
||||||
commandExecutor.postOperationMode(mode);
|
localCommandExecutor.postOperationMode(mode);
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
logger.warn("{}: OperationMode \"{}\" is not valid!", getDeviceName(), cmd);
|
logger.warn("{}: OperationMode \"{}\" is not valid!", getDeviceName(), cmd);
|
||||||
}
|
}
|
||||||
@ -220,28 +227,28 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
break;
|
break;
|
||||||
case CHANNEL_PLAYER_CONTROL:
|
case CHANNEL_PLAYER_CONTROL:
|
||||||
if ((command instanceof PlayPauseType) || (command instanceof NextPreviousType)) {
|
if ((command instanceof PlayPauseType) || (command instanceof NextPreviousType)) {
|
||||||
commandExecutor.postPlayerControl(command);
|
localCommandExecutor.postPlayerControl(command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_PRESET:
|
case CHANNEL_PRESET:
|
||||||
if (command instanceof DecimalType) {
|
if (command instanceof DecimalType) {
|
||||||
commandExecutor.postPreset((DecimalType) command);
|
localCommandExecutor.postPreset((DecimalType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_BASS:
|
case CHANNEL_BASS:
|
||||||
if (command instanceof DecimalType) {
|
if (command instanceof DecimalType) {
|
||||||
commandExecutor.postBass((DecimalType) command);
|
localCommandExecutor.postBass((DecimalType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CHANNEL_SAVE_AS_PRESET:
|
case CHANNEL_SAVE_AS_PRESET:
|
||||||
if (command instanceof DecimalType) {
|
if (command instanceof DecimalType) {
|
||||||
commandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command);
|
localCommandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
|
||||||
}
|
}
|
||||||
@ -251,7 +258,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
String cmd = command.toString().toUpperCase().trim();
|
String cmd = command.toString().toUpperCase().trim();
|
||||||
try {
|
try {
|
||||||
RemoteKeyType keyCommand = RemoteKeyType.valueOf(cmd);
|
RemoteKeyType keyCommand = RemoteKeyType.valueOf(cmd);
|
||||||
commandExecutor.postRemoteKey(keyCommand);
|
localCommandExecutor.postRemoteKey(keyCommand);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
logger.debug("{}: Unhandled remote key: {}", getDeviceName(), cmd);
|
logger.debug("{}: Unhandled remote key: {}", getDeviceName(), cmd);
|
||||||
}
|
}
|
||||||
@ -262,7 +269,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
ChannelTypeUID chTypeUid = channel.getChannelTypeUID();
|
ChannelTypeUID chTypeUid = channel.getChannelTypeUID();
|
||||||
if (chTypeUid != null) {
|
if (chTypeUid != null) {
|
||||||
switch (channel.getChannelTypeUID().getId()) {
|
switch (chTypeUid.getId()) {
|
||||||
case CHANNEL_NOTIFICATION_SOUND:
|
case CHANNEL_NOTIFICATION_SOUND:
|
||||||
String appKey = Objects.toString(getConfig().get(BoseSoundTouchConfiguration.APP_KEY),
|
String appKey = Objects.toString(getConfig().get(BoseSoundTouchConfiguration.APP_KEY),
|
||||||
null);
|
null);
|
||||||
@ -273,8 +280,8 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
.getConfiguration()
|
.getConfiguration()
|
||||||
.as(BoseSoundTouchNotificationChannelConfiguration.class);
|
.as(BoseSoundTouchNotificationChannelConfiguration.class);
|
||||||
if (!url.isEmpty()) {
|
if (!url.isEmpty()) {
|
||||||
commandExecutor.playNotificationSound(appKey, notificationConfiguration,
|
localCommandExecutor.playNotificationSound(appKey,
|
||||||
url);
|
notificationConfiguration, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -295,7 +302,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the CommandExecutor of this handler
|
* @return the CommandExecutor of this handler
|
||||||
*/
|
*/
|
||||||
public CommandExecutor getCommandExecutor() {
|
public @Nullable CommandExecutor getCommandExecutor() {
|
||||||
return commandExecutor;
|
return commandExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,7 +311,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the Session this handler has opened
|
* @return the Session this handler has opened
|
||||||
*/
|
*/
|
||||||
public Session getSession() {
|
public @Nullable Session getSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +320,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the name of the device delivered from itself
|
* @return the name of the device delivered from itself
|
||||||
*/
|
*/
|
||||||
public String getDeviceName() {
|
public @Nullable String getDeviceName() {
|
||||||
return getThing().getProperties().get(DEVICE_INFO_NAME);
|
return getThing().getProperties().get(DEVICE_INFO_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +329,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the type of the device delivered from itself
|
* @return the type of the device delivered from itself
|
||||||
*/
|
*/
|
||||||
public String getDeviceType() {
|
public @Nullable String getDeviceType() {
|
||||||
return getThing().getProperties().get(DEVICE_INFO_TYPE);
|
return getThing().getProperties().get(DEVICE_INFO_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,7 +338,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the MAC Address of this device (in format "123456789ABC")
|
* @return the MAC Address of this device (in format "123456789ABC")
|
||||||
*/
|
*/
|
||||||
public String getMacAddress() {
|
public @Nullable String getMacAddress() {
|
||||||
return ((String) getThing().getConfiguration().get(BoseSoundTouchConfiguration.MAC_ADDRESS)).replaceAll(":",
|
return ((String) getThing().getConfiguration().get(BoseSoundTouchConfiguration.MAC_ADDRESS)).replaceAll(":",
|
||||||
"");
|
"");
|
||||||
}
|
}
|
||||||
@ -341,7 +348,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
*
|
*
|
||||||
* @return the IP Address of this device
|
* @return the IP Address of this device
|
||||||
*/
|
*/
|
||||||
public String getIPAddress() {
|
public @Nullable String getIPAddress() {
|
||||||
return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST);
|
return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +366,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(Session session) {
|
public void onWebSocketConnect(@Nullable Session session) {
|
||||||
logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session);
|
logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session);
|
||||||
this.session = session;
|
this.session = session;
|
||||||
commandExecutor = new CommandExecutor(this);
|
commandExecutor = new CommandExecutor(this);
|
||||||
@ -367,88 +374,106 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketError(Throwable e) {
|
public void onWebSocketError(@Nullable Throwable e) {
|
||||||
logger.debug("{}: Error during websocket communication: {}", getDeviceName(), e.getMessage(), e);
|
Throwable localThrowable = (e != null) ? e
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
: new IllegalStateException("Null Exception passed to onWebSocketError");
|
||||||
if (commandExecutor != null) {
|
logger.debug("{}: Error during websocket communication: {}", getDeviceName(), localThrowable.getMessage(),
|
||||||
commandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
localThrowable);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, localThrowable.getMessage());
|
||||||
|
CommandExecutor localCommandExecutor = commandExecutor;
|
||||||
|
if (localCommandExecutor != null) {
|
||||||
|
localCommandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
||||||
commandExecutor = null;
|
commandExecutor = null;
|
||||||
}
|
}
|
||||||
if (session != null) {
|
Session localSession = session;
|
||||||
session.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + e.getMessage());
|
if (localSession != null) {
|
||||||
|
localSession.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + localThrowable.getMessage());
|
||||||
session = null;
|
session = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketText(String msg) {
|
public void onWebSocketText(@Nullable String msg) {
|
||||||
logger.debug("{}: onWebSocketText('{}')", getDeviceName(), msg);
|
logger.debug("{}: onWebSocketText('{}')", getDeviceName(), msg);
|
||||||
try {
|
try {
|
||||||
xmlResponseProcessor.handleMessage(msg);
|
String localMessage = msg;
|
||||||
|
if (localMessage != null) {
|
||||||
|
xmlResponseProcessor.handleMessage(localMessage);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("{}: Could not parse XML from string '{}'.", getDeviceName(), msg, e);
|
logger.warn("{}: Could not parse XML from string '{}'.", getDeviceName(), msg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketBinary(byte[] arr, int pos, int len) {
|
public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) {
|
||||||
// we don't expect binary data so just dump if we get some...
|
// we don't expect binary data so just dump if we get some...
|
||||||
logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), pos, len, Arrays.toString(arr));
|
logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), offset, len, Arrays.toString(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketClose(int code, String reason) {
|
public void onWebSocketClose(int code, @Nullable String reason) {
|
||||||
logger.debug("{}: onClose({}, '{}')", getDeviceName(), code, reason);
|
logger.debug("{}: onClose({}, '{}')", getDeviceName(), code, reason);
|
||||||
missedPongsCount = 0;
|
missedPongsCount = 0;
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
|
||||||
if (commandExecutor != null) {
|
CommandExecutor localCommandExecutor = commandExecutor;
|
||||||
commandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
if (localCommandExecutor != null) {
|
||||||
|
localCommandExecutor.postOperationMode(OperationModeType.OFFLINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketFrame(Frame frame) {
|
public void onWebSocketFrame(@Nullable Frame frame) {
|
||||||
if (frame.getType() == Type.PONG) {
|
Frame localFrame = frame;
|
||||||
|
if (localFrame != null) {
|
||||||
|
if (localFrame.getType() == Type.PONG) {
|
||||||
missedPongsCount = 0;
|
missedPongsCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private synchronized void openConnection() {
|
private synchronized void openConnection() {
|
||||||
closeConnection();
|
closeConnection();
|
||||||
try {
|
try {
|
||||||
client = new WebSocketClient();
|
WebSocketClient localClient = new WebSocketClient();
|
||||||
// we need longer timeouts for web socket.
|
// we need longer timeouts for web socket.
|
||||||
client.setMaxIdleTimeout(360 * 1000);
|
localClient.setMaxIdleTimeout(360 * 1000);
|
||||||
// Port seems to be hard coded, therefore no user input or discovery is necessary
|
// Port seems to be hard coded, therefore no user input or discovery is necessary
|
||||||
String wsUrl = "ws://" + getIPAddress() + ":8080/";
|
String wsUrl = "ws://" + getIPAddress() + ":8080/";
|
||||||
logger.debug("{}: Connecting to: {}", getDeviceName(), wsUrl);
|
logger.debug("{}: Connecting to: {}", getDeviceName(), wsUrl);
|
||||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||||
request.setSubProtocols("gabbo");
|
request.setSubProtocols("gabbo");
|
||||||
client.setStopTimeout(1000);
|
localClient.setStopTimeout(1000);
|
||||||
client.start();
|
localClient.start();
|
||||||
sessionFuture = client.connect(this, new URI(wsUrl), request);
|
sessionFuture = localClient.connect(this, new URI(wsUrl), request);
|
||||||
|
client = localClient;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
onWebSocketError(e);
|
onWebSocketError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void closeConnection() {
|
private synchronized void closeConnection() {
|
||||||
if (session != null) {
|
Session localSession = this.session;
|
||||||
|
if (localSession != null) {
|
||||||
try {
|
try {
|
||||||
session.close(StatusCode.NORMAL, "Binding shutdown");
|
localSession.close(StatusCode.NORMAL, "Binding shutdown");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
||||||
e.getClass().getName(), e.getMessage());
|
e.getClass().getName(), e.getMessage());
|
||||||
}
|
}
|
||||||
session = null;
|
session = null;
|
||||||
}
|
}
|
||||||
if (sessionFuture != null && !sessionFuture.isDone()) {
|
Future<?> localSessionFuture = sessionFuture;
|
||||||
sessionFuture.cancel(true);
|
if (localSessionFuture != null) {
|
||||||
|
if (!localSessionFuture.isDone()) {
|
||||||
|
localSessionFuture.cancel(true);
|
||||||
}
|
}
|
||||||
if (client != null) {
|
}
|
||||||
|
WebSocketClient localClient = client;
|
||||||
|
if (localClient != null) {
|
||||||
try {
|
try {
|
||||||
client.stop();
|
localClient.stop();
|
||||||
client.destroy();
|
localClient.destroy();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
|
||||||
e.getClass().getName(), e.getMessage());
|
e.getClass().getName(), e.getMessage());
|
||||||
@ -464,12 +489,13 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
|| commandExecutor == null) {
|
|| commandExecutor == null) {
|
||||||
openConnection(); // try to reconnect....
|
openConnection(); // try to reconnect....
|
||||||
}
|
}
|
||||||
|
Session localSession = this.session;
|
||||||
if (getThing().getStatus() == ThingStatus.ONLINE && this.session != null && this.session.isOpen()) {
|
if (localSession != null) {
|
||||||
|
if (getThing().getStatus() == ThingStatus.ONLINE && localSession.isOpen()) {
|
||||||
try {
|
try {
|
||||||
this.session.getRemote().sendPing(null);
|
localSession.getRemote().sendPing(null);
|
||||||
missedPongsCount++;
|
missedPongsCount++;
|
||||||
} catch (IOException | NullPointerException e) {
|
} catch (IOException e) {
|
||||||
onWebSocketError(e);
|
onWebSocketError(e);
|
||||||
closeConnection();
|
closeConnection();
|
||||||
openConnection();
|
openConnection();
|
||||||
@ -484,6 +510,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshPresetChannel() {
|
public void refreshPresetChannel() {
|
||||||
List<StateOption> stateOptions = presetContainer.getAllPresets().stream().map(e -> e.toStateOption())
|
List<StateOption> stateOptions = presetContainer.getAllPresets().stream().map(e -> e.toStateOption())
|
||||||
@ -494,7 +521,7 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket
|
|||||||
public void handleGroupUpdated(BoseSoundTouchConfiguration masterPlayerConfiguration) {
|
public void handleGroupUpdated(BoseSoundTouchConfiguration masterPlayerConfiguration) {
|
||||||
String deviceId = getMacAddress();
|
String deviceId = getMacAddress();
|
||||||
|
|
||||||
if (masterPlayerConfiguration != null && masterPlayerConfiguration.macAddress != null) {
|
if (masterPlayerConfiguration.macAddress != null) {
|
||||||
// Stereo pair
|
// Stereo pair
|
||||||
if (Objects.equals(masterPlayerConfiguration.macAddress, deviceId)) {
|
if (Objects.equals(masterPlayerConfiguration.macAddress, deviceId)) {
|
||||||
if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
|
if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user