diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java index 7a01af606..65a1ad4d4 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/CommandExecutor.java @@ -155,7 +155,6 @@ public class CommandExecutor implements AvailableSources { contentItem.setPresetID(presetID); currentContentItem = contentItem; - } updateOperatingValues(); } diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java index 502680e58..56b390fad 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseHandler.java @@ -97,11 +97,6 @@ public class XMLResponseHandler extends DefaultHandler { // showing a // warning for unhandled states - XMLHandlerState localState = null; - if (stateMap != null) { - localState = stateMap.get(localName); - } - switch (curState) { case INIT: if ("updates".equals(localName)) { @@ -112,10 +107,13 @@ public class XMLResponseHandler extends DefaultHandler { state = XMLHandlerState.Unprocessed; } } else { + XMLHandlerState localState = stateMap.get(localName); if (localState == null) { - logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, + logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, localName); state = XMLHandlerState.Unprocessed; + } else { + state = localState; } } break; @@ -196,9 +194,11 @@ public class XMLResponseHandler extends DefaultHandler { state = XMLHandlerState.Presets; } else if ("group".equals(localName)) { this.masterDeviceId = new BoseSoundTouchConfiguration(); + state = stateMap.get(localName); } else { - if (localState == null) { - logger.debug("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, + state = stateMap.get(localName); + if (state == null) { + logger.warn("{}: Unhandled XML entity during {}: '{}", handler.getDeviceName(), curState, localName); state = XMLHandlerState.Unprocessed; @@ -366,16 +366,12 @@ public class XMLResponseHandler extends DefaultHandler { if (contentItem == null) { contentItem = new ContentItem(); } - String source = ""; - String location = ""; - String sourceAccount = ""; - Boolean isPresetable = false; if (attributes != null) { - source = attributes.getValue("source"); - sourceAccount = attributes.getValue("sourceAccount"); - location = attributes.getValue("location"); - isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable")); + String source = attributes.getValue("source"); + String location = attributes.getValue("location"); + String sourceAccount = attributes.getValue("sourceAccount"); + Boolean isPresetable = Boolean.parseBoolean(attributes.getValue("isPresetable")); if (source != null) { contentItem.setSource(source); diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java index 533e53821..bd180faaf 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/XMLResponseProcessor.java @@ -47,6 +47,7 @@ public class XMLResponseProcessor { public void handleMessage(String msg) throws SAXException, IOException, ParserConfigurationException { SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setNamespaceAware(true); SAXParser parser = parserFactory.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java index c5a1e8360..c69288c80 100644 --- a/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java +++ b/bundles/org.openhab.binding.bosesoundtouch/src/main/java/org/openhab/binding/bosesoundtouch/internal/handler/BoseSoundTouchHandler.java @@ -306,6 +306,14 @@ public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocket return commandExecutor; } + /** + * Sets the CommandExecutor of this handler + * + */ + public void setCommandExecutor(@Nullable CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + } + /** * Returns the Session this handler has opened * diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java new file mode 100644 index 000000000..f7a7d954d --- /dev/null +++ b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/SoundTouch20Tests.java @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.bosesoundtouch.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; + +import java.text.MessageFormat; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.bosesoundtouch.internal.handler.BoseSoundTouchHandler; +import org.openhab.binding.bosesoundtouch.internal.handler.InMemmoryContentStorage; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StringListType; +import org.openhab.core.storage.Storage; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; + +/** + * + * @author Leo Siepel - Initial contribution + * + */ +@ExtendWith(MockitoExtension.class) +@NonNullByDefault +public class SoundTouch20Tests { + + private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback; + private @NonNullByDefault({}) Thing soundTouchThing; + private @NonNullByDefault({}) BoseSoundTouchHandler thingHandler; + private @NonNullByDefault({}) XMLResponseProcessor processor; + private ThingUID thingUID = new ThingUID(BoseSoundTouchBindingConstants.BINDING_ID, "soundtouch20"); + private ChannelUID volumeChannelUID = new ChannelUID(thingUID, BoseSoundTouchBindingConstants.CHANNEL_VOLUME); + private ChannelUID presetChannelUID = new ChannelUID(thingUID, BoseSoundTouchBindingConstants.CHANNEL_PRESET); + private Storage<@NonNull ContentItem> storage = new InMemmoryContentStorage(); + private @Mock @NonNullByDefault({}) BoseStateDescriptionOptionProvider stateDescriptionProvider; + + @BeforeEach + public void initialize() { + // arrange + Configuration config = new Configuration(); + config.put(BoseSoundTouchConfiguration.MAC_ADDRESS, "B0D5CC1AAAA1"); + + soundTouchThing = ThingBuilder.create(BoseSoundTouchBindingConstants.BST_20_THING_TYPE_UID, thingUID) + .withConfiguration(config).withChannel(ChannelBuilder.create(volumeChannelUID, "Number").build()) + .withChannel(ChannelBuilder.create(presetChannelUID, "Number").build()).build(); + + PresetContainer container = new PresetContainer(storage); + thingHandler = new BoseSoundTouchHandler(soundTouchThing, container, stateDescriptionProvider); + processor = new XMLResponseProcessor(thingHandler); + } + + private void processIncomingMessage(String mesage) { + try { + processor.handleMessage(mesage); + } catch (Exception e) { + assert false : MessageFormat.format("handleMessage throws an exception: {0} Stacktrace: {1}", + e.getMessage(), e.getStackTrace()); + } + } + + @Test + public void configurationPropertyUpdated() { + // arange + CommandExecutor executor = new CommandExecutor(thingHandler); + thingHandler.setCommandExecutor(executor); + String message = "
livingroomSoundTouch 203504027SCM27.0.6.46330.5043500 epdbuild.trunk.hepdswbld04.2022-08-04T11:20:29U6148010803720048000100PackagedProduct069430P5227013812https://streaming.bose.comB0D5CC1AAAA1192.168.1.15CF821E2FD76192.168.1.1sm2spottynormalGBGB
"; + + // act + processIncomingMessage(message); + + // assert + assertEquals("27.0.6.46330.5043500", + soundTouchThing.getProperties().get(org.openhab.core.thing.Thing.PROPERTY_FIRMWARE_VERSION)); + } + + @Test + public void channelVolumeUpdated() { + // arrange + CommandExecutor executor = new CommandExecutor(thingHandler); + + thingHandler.setCommandExecutor(executor); + Mockito.when(thingHandlerCallback.isChannelLinked((ChannelUID) notNull())).thenReturn(true); + thingHandler.setCallback(thingHandlerCallback); + String message = "27"; + + // act + processIncomingMessage(message); + + // assert + Mockito.verify(thingHandlerCallback).stateUpdated(eq(volumeChannelUID), eq(new PercentType("27"))); + } + + @Test + @Disabled + public void channelPresetUpdated() { + // arrange + CommandExecutor executor = new CommandExecutor(thingHandler); + + thingHandler.setCommandExecutor(executor); + Mockito.when(thingHandlerCallback.isChannelLinked((ChannelUID) notNull())).thenReturn(true); + thingHandler.setCallback(thingHandlerCallback); + String message = "Radio FM1http://cdn-profiles.tunein.com/s25077/images/logoq.jpgMedicine At MidnightConcrete & GoldSWR3http://radiotime-logos.s3.amazonaws.com/s24896q.pngSRF 3http://radiotime-logos.s3.amazonaws.com/s24862q.pngSonic Highways"; + + // act + processIncomingMessage(message); + + // assert + // TODO: check if preset channels have changed + Mockito.verify(thingHandlerCallback).stateUpdated(eq(presetChannelUID), eq(new StringListType("27"))); + } +} diff --git a/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java new file mode 100644 index 000000000..2b7a3c042 --- /dev/null +++ b/bundles/org.openhab.binding.bosesoundtouch/src/test/java/org/openhab/binding/bosesoundtouch/internal/handler/InMemmoryContentStorage.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.bosesoundtouch.internal.handler; + +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.bosesoundtouch.internal.ContentItem; +import org.openhab.core.storage.Storage; + +/** + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class InMemmoryContentStorage implements Storage { + Map items = new TreeMap<>(); + + public InMemmoryContentStorage() { + } + + @Override + public @Nullable ContentItem put(String key, @Nullable ContentItem value) { + return items.put(key, value); + } + + @Override + public @Nullable ContentItem remove(String key) { + return items.remove(key); + } + + @Override + public boolean containsKey(String key) { + return items.containsKey(key); + } + + @Override + public @Nullable ContentItem get(String key) { + return items.get(key); + } + + @Override + public Collection<@NonNull String> getKeys() { + return items.keySet(); + } + + @Override + public Collection<@Nullable ContentItem> getValues() { + return items.values(); + } +}